From 1d46ddc3f50ae12520e4f083a98ff17a61658359 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Fri, 14 May 2021 18:36:38 +0300 Subject: [PATCH 001/110] added core and v2 pods and started implementation --- .../Authenticator.xcodeproj/project.pbxproj | 8 + .../ConnectorWebViewController.swift | 3 +- Example/Podfile | 6 +- Example/Podfile.lock | 20 +- .../Classes/API/Headers.swift | 71 +++ .../Classes/API/SEEncryptedData.swift | 63 +++ .../Classes/API/SENetConstants.swift | 33 ++ .../Classes/API/SENetKeys.swift | 78 ++++ .../Classes/API/SerializableResponse.swift | 27 ++ .../Classes/Crypto/SECryptoHelper.swift | 407 ++++++++++++++++++ .../Classes/Crypto/SECryptoHelperError.swift | 82 ++++ .../Classes/Crypto/SETagHelper.swift | 29 ++ .../Classes/DictionaryExtensions.swift | 47 ++ .../Helpers/ParametersSerializer.swift | 42 ++ .../Classes/Helpers/TypeAliases.swift | 34 ++ .../Classes/Routers/Routable.swift | 73 ++++ .../Classes/SEWebView/SEWebView.swift | 87 ++++ .../Classes/URLExtensions.swift | 31 ++ .../SaltedgeAuthenticatorCore.podspec | 31 ++ SaltedgeAuthenticatorSDK.podspec | 3 +- .../Classes/API/Routers/SEActionRouter.swift | 1 + .../Classes/SEWebView/SEWebView.swift | 130 +++--- .../Classes/RequestParametersBuilder.swift | 23 + .../Classes/Routers/SEConnectionRouter.swift | 94 ++++ .../SaltedgeAuthenticatorSDKv2.podspec | 32 ++ 25 files changed, 1383 insertions(+), 72 deletions(-) create mode 100644 SaltedgeAuthenticatorCore/Classes/API/Headers.swift create mode 100644 SaltedgeAuthenticatorCore/Classes/API/SEEncryptedData.swift create mode 100644 SaltedgeAuthenticatorCore/Classes/API/SENetConstants.swift create mode 100644 SaltedgeAuthenticatorCore/Classes/API/SENetKeys.swift create mode 100644 SaltedgeAuthenticatorCore/Classes/API/SerializableResponse.swift create mode 100644 SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelper.swift create mode 100644 SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelperError.swift create mode 100644 SaltedgeAuthenticatorCore/Classes/Crypto/SETagHelper.swift create mode 100644 SaltedgeAuthenticatorCore/Classes/DictionaryExtensions.swift create mode 100644 SaltedgeAuthenticatorCore/Classes/Helpers/ParametersSerializer.swift create mode 100644 SaltedgeAuthenticatorCore/Classes/Helpers/TypeAliases.swift create mode 100644 SaltedgeAuthenticatorCore/Classes/Routers/Routable.swift create mode 100644 SaltedgeAuthenticatorCore/Classes/SEWebView/SEWebView.swift create mode 100644 SaltedgeAuthenticatorCore/Classes/URLExtensions.swift create mode 100644 SaltedgeAuthenticatorCore/SaltedgeAuthenticatorCore.podspec create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/Routers/SEConnectionRouter.swift create mode 100644 SaltedgeAuthenticatorSDKv2/SaltedgeAuthenticatorSDKv2.podspec diff --git a/Example/Authenticator.xcodeproj/project.pbxproj b/Example/Authenticator.xcodeproj/project.pbxproj index add64a62..8428fe29 100644 --- a/Example/Authenticator.xcodeproj/project.pbxproj +++ b/Example/Authenticator.xcodeproj/project.pbxproj @@ -1602,7 +1602,9 @@ "${BUILT_PRODUCTS_DIR}/Realm/Realm.framework", "${BUILT_PRODUCTS_DIR}/RealmSwift/RealmSwift.framework", "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework", + "${BUILT_PRODUCTS_DIR}/SaltedgeAuthenticatorCore/SEAuthenticatorCore.framework", "${BUILT_PRODUCTS_DIR}/SaltedgeAuthenticatorSDK/SEAuthenticator.framework", + "${BUILT_PRODUCTS_DIR}/SaltedgeAuthenticatorSDKv2/SEAuthenticatorV2.framework", "${BUILT_PRODUCTS_DIR}/TinyConstraints/TinyConstraints.framework", "${BUILT_PRODUCTS_DIR}/Valet/Valet.framework", "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework", @@ -1621,7 +1623,9 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RealmSwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SEAuthenticatorCore.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SEAuthenticator.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SEAuthenticatorV2.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TinyConstraints.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Valet.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework", @@ -1709,7 +1713,9 @@ "${BUILT_PRODUCTS_DIR}/Realm/Realm.framework", "${BUILT_PRODUCTS_DIR}/RealmSwift/RealmSwift.framework", "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework", + "${BUILT_PRODUCTS_DIR}/SaltedgeAuthenticatorCore/SEAuthenticatorCore.framework", "${BUILT_PRODUCTS_DIR}/SaltedgeAuthenticatorSDK/SEAuthenticator.framework", + "${BUILT_PRODUCTS_DIR}/SaltedgeAuthenticatorSDKv2/SEAuthenticatorV2.framework", "${BUILT_PRODUCTS_DIR}/TinyConstraints/TinyConstraints.framework", "${BUILT_PRODUCTS_DIR}/Valet/Valet.framework", "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework", @@ -1730,7 +1736,9 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RealmSwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SEAuthenticatorCore.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SEAuthenticator.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SEAuthenticatorV2.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TinyConstraints.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Valet.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework", diff --git a/Example/Authenticator/View Controllers/ConnectorWebViewController.swift b/Example/Authenticator/View Controllers/ConnectorWebViewController.swift index 855ccfb6..a0ebb6db 100644 --- a/Example/Authenticator/View Controllers/ConnectorWebViewController.swift +++ b/Example/Authenticator/View Controllers/ConnectorWebViewController.swift @@ -22,7 +22,8 @@ import UIKit import WebKit -import SEAuthenticator +import SEAuthenticatorCore +//import SEAuthenticator protocol ConnectorWebViewControllerDelegate: WKWebViewControllerDelegate { func connectorConfirmed(url: URL, accessToken: AccessToken) diff --git a/Example/Podfile b/Example/Podfile index 0eebad25..13132ec6 100644 --- a/Example/Podfile +++ b/Example/Podfile @@ -2,7 +2,9 @@ platform :ios, '10.0' use_frameworks! def shared_pods + pod 'SaltedgeAuthenticatorCore', :path => '../SaltedgeAuthenticatorCore' pod 'SaltedgeAuthenticatorSDK', :path => '../' + pod 'SaltedgeAuthenticatorSDKv2', :path => '../SaltedgeAuthenticatorSDKv2' pod 'ReachabilitySwift' pod 'TinyConstraints' pod 'RealmSwift', '5.5.1', :inhibit_warnings => true @@ -26,8 +28,8 @@ post_install do |pi| pi.pods_project.targets.each do |t| t.build_configurations.each do |config| config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '10.0' - config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64' - config.build_settings['CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER'] = 'NO' + config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64' + config.build_settings['CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER'] = 'NO' end end end diff --git a/Example/Podfile.lock b/Example/Podfile.lock index 97b11e3f..ac47162b 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -100,8 +100,14 @@ PODS: - Realm/Headers (5.5.1) - RealmSwift (5.5.1): - Realm (= 5.5.1) - - SaltedgeAuthenticatorSDK (1.1.1): + - SaltedgeAuthenticatorCore (1.1.0): - CryptoSwift + - SaltedgeAuthenticatorSDK (1.1.0): + - CryptoSwift + - SaltedgeAuthenticatorCore + - SaltedgeAuthenticatorSDKv2 (1.1.0): + - CryptoSwift + - SaltedgeAuthenticatorCore - SDWebImage (5.10.4): - SDWebImage/Core (= 5.10.4) - SDWebImage/Core (5.10.4) @@ -115,7 +121,9 @@ DEPENDENCIES: - Quick - ReachabilitySwift - RealmSwift (= 5.5.1) + - SaltedgeAuthenticatorCore (from `../SaltedgeAuthenticatorCore`) - SaltedgeAuthenticatorSDK (from `../`) + - SaltedgeAuthenticatorSDKv2 (from `../SaltedgeAuthenticatorSDKv2`) - SDWebImage (= 5.10.4) - TinyConstraints - Valet (~> 3.2.8) @@ -144,8 +152,12 @@ SPEC REPOS: - Valet EXTERNAL SOURCES: + SaltedgeAuthenticatorCore: + :path: "../SaltedgeAuthenticatorCore" SaltedgeAuthenticatorSDK: :path: "../" + SaltedgeAuthenticatorSDKv2: + :path: "../SaltedgeAuthenticatorSDKv2" SPEC CHECKSUMS: CryptoSwift: 7cc902df1784de3b389a387756c7d710f197730c @@ -165,11 +177,13 @@ SPEC CHECKSUMS: ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 Realm: c2ffe0667f9c98c9ba65f608ba85684d39826145 RealmSwift: 5a35c1c35c3e1925e6fcd092c881d5cc4a826bff - SaltedgeAuthenticatorSDK: b9260860f0e67fd169e8db903e92839584e1ab69 + SaltedgeAuthenticatorCore: bf9c91b81449fb7ccd23581ab84e1eeff5020647 + SaltedgeAuthenticatorSDK: 4fd566984678e8d936cae541aee82c89b44422e2 + SaltedgeAuthenticatorSDKv2: 2c3536ff2426d12c2624bf9524502f61a70ab458 SDWebImage: c666b97e1fa9c64b4909816a903322018f0a9c84 TinyConstraints: 8dd91295e56797648c7bc335dd20e1d91ec4c192 Valet: 16d0537d70db79d9ba953b9060b5da4fb8004e51 -PODFILE CHECKSUM: 402996a4c66dc49d77bdf56b16a259e31c950d1f +PODFILE CHECKSUM: 7f15a08d33a1af46d3f787184ec65b0ed568eed4 COCOAPODS: 1.10.1 diff --git a/SaltedgeAuthenticatorCore/Classes/API/Headers.swift b/SaltedgeAuthenticatorCore/Classes/API/Headers.swift new file mode 100644 index 00000000..d998d4e8 --- /dev/null +++ b/SaltedgeAuthenticatorCore/Classes/API/Headers.swift @@ -0,0 +1,71 @@ +// +// Headers.swift +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation + +struct HeadersKeys { + static let accessToken = "Access-Token" + static let accept = "Accept" + static let acceptLanguage = "Accept-Language" + static let contentType = "Content-Type" + static let expiresAt = "Expires-At" + static let signature = "Signature" + static let geolocation = "GEO-Location" + static let authorizationType = "Authorization-Type" +} + +public struct Headers { + public static func signedRequestHeaders(token: String, expiresAt: Int, signature: String?, appLanguage: String) -> [String: String] { + guard let signedMessage = signature else { return authorizedRequestHeaders(token: token, appLanguage: appLanguage) } + + return authorizedRequestHeaders(token: token, appLanguage: appLanguage).merge( + with: [ + HeadersKeys.expiresAt: "\(expiresAt)", + HeadersKeys.signature: "\(signedMessage)" + ] + ) + } + + public static func authorizedRequestHeaders(token: String, appLanguage: String) -> [String: String] { + return requestHeaders(with: appLanguage).merge(with: [HeadersKeys.accessToken: token]) + } + + public static func requestHeaders(with appLanguage: String) -> [String: String] { + return [ + HeadersKeys.accept: "application/json", + HeadersKeys.acceptLanguage: appLanguage, + HeadersKeys.contentType: "application/json" + ] + } +} + +extension Dictionary where Key == String, Value == String { + func addLocationHeader(geolocation: String?) -> [String: String] { + guard let geolocation = geolocation else { return self } + + return self.merge(with: [ HeadersKeys.geolocation: geolocation ]) + } + + func addAuthorizationTypeHeader(authorizationType: String) -> [String: String] { + return self.merge(with: [ HeadersKeys.authorizationType: authorizationType ]) + } +} diff --git a/SaltedgeAuthenticatorCore/Classes/API/SEEncryptedData.swift b/SaltedgeAuthenticatorCore/Classes/API/SEEncryptedData.swift new file mode 100644 index 00000000..1099f861 --- /dev/null +++ b/SaltedgeAuthenticatorCore/Classes/API/SEEncryptedData.swift @@ -0,0 +1,63 @@ +// +// SEEncryptedData.swift +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation + +public struct SEEncryptedData: SerializableResponse, Equatable { + private let defaultAlgorithm = "AES-256-CBC" + + public let data: String + public let key: String + public let iv: String + public var connectionId: String? + + public init(data: String, key: String, iv: String) { + self.data = data + self.key = key + self.iv = iv + } + + public init?(_ value: Any) { + if let dict = value as? [String: Any], + let data = dict[SENetKeys.data] as? String, + let key = dict[SENetKeys.key] as? String, + let iv = dict[SENetKeys.iv] as? String, + let algorithm = dict[SENetKeys.algorithm] as? String, + algorithm == defaultAlgorithm { + self.data = data + self.key = key + self.iv = iv + if let connectionId = dict[SENetKeys.connectionId] as? String { + self.connectionId = connectionId + } + } else { + return nil + } + } + + public static func == (lhs: SEEncryptedData, rhs: SEEncryptedData) -> Bool { + return lhs.data == rhs.data && + lhs.key == rhs.key && + lhs.iv == rhs.iv && + lhs.connectionId == rhs.connectionId + } +} diff --git a/SaltedgeAuthenticatorCore/Classes/API/SENetConstants.swift b/SaltedgeAuthenticatorCore/Classes/API/SENetConstants.swift new file mode 100644 index 00000000..46f39a90 --- /dev/null +++ b/SaltedgeAuthenticatorCore/Classes/API/SENetConstants.swift @@ -0,0 +1,33 @@ +// +// SENetConstants.swift +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation + +struct SENetConstants { + static var oauthRedirectUrl: String { + return "authenticator://oauth/redirect" + } + + static func hasRedirectUrl(_ urlString: String) -> Bool { + return urlString.starts(with: oauthRedirectUrl) + } +} diff --git a/SaltedgeAuthenticatorCore/Classes/API/SENetKeys.swift b/SaltedgeAuthenticatorCore/Classes/API/SENetKeys.swift new file mode 100644 index 00000000..7d62b5b4 --- /dev/null +++ b/SaltedgeAuthenticatorCore/Classes/API/SENetKeys.swift @@ -0,0 +1,78 @@ +// +// SENetKeys.swift +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation + +public struct SENetKeys { + public static let aps = "aps" + + public static let data = "data" + public static let id = "id" + public static let name = "name" + public static let code = "code" + public static let logoUrl = "logo_url" + public static let supportEmail = "support_email" + public static let version = "version" + public static let geolocationRequired = "geolocation_required" + + public static let configuration = "configuration" + public static let connectQuery = "connect_query" + public static let connectUrl = "connect_url" + + public static let title = "title" + public static let description = "description" + public static let message = "message" + + public static let success = "success" + + public static let accessToken = "access_token" + + public static let createdAt = "created_at" + public static let expiresAt = "expires_at" + public static let redirectUrl = "redirect_url" + + public static let key = "key" + public static let iv = "iv" + + public static let authorizationId = "authorization_id" + public static let authorizationCode = "authorization_code" + + public static let connectionId = "connection_id" + public static let algorithm = "algorithm" + + public static let consentId = "consent_id" + + public static let errorClass = "error_class" + public static let errorMessage = "error_message" + + public static let userId = "user_id" + public static let consentManagement = "consent_management" + public static let tppName = "tpp_name" + public static let consentType = "consent_type" + public static let accounts = "accounts" + public static let sharedData = "shared_data" + public static let accountNumber = "account_number" + public static let sortCode = "sort_code" + public static let iban = "iban" + public static let balance = "balance" + public static let transactions = "transactions" +} diff --git a/SaltedgeAuthenticatorCore/Classes/API/SerializableResponse.swift b/SaltedgeAuthenticatorCore/Classes/API/SerializableResponse.swift new file mode 100644 index 00000000..0156b9c2 --- /dev/null +++ b/SaltedgeAuthenticatorCore/Classes/API/SerializableResponse.swift @@ -0,0 +1,27 @@ +// +// SerializableResponse.swift +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation + +public protocol SerializableResponse { + init?(_ value: Any) +} diff --git a/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelper.swift b/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelper.swift new file mode 100644 index 00000000..d18033c0 --- /dev/null +++ b/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelper.swift @@ -0,0 +1,407 @@ +// +// SECryptoHelper.swift +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import CryptoSwift + +public typealias KeyTag = String +public typealias KeyPair = (publicKey: SecKey, privateKey: SecKey) + +public struct SECryptoHelper { + // MARK: - Public Methods + public static func createKeyPair(with tag: KeyTag) -> KeyPair? { + SecKeyHelper.deleteKey(tag) + SecKeyHelper.deleteKey(tag.privateTag) + + return SecKeyHelper.generateKeyPair(tag: tag) + } + + public static func createKey(fromFile name: String, isPublic: Bool, tag: String) throws -> SecKey { + SecKeyHelper.deleteKey(tag) + SecKeyHelper.deleteKey(tag.privateTag) + + return try SecKeyHelper.createKey(fromFile: name, isPublic: isPublic, tag: tag) + } + + @discardableResult + public static func deleteKeyPair(with tag: KeyTag) -> Bool { + SecKeyHelper.deleteKey(tag.privateTag) + + return SecKeyHelper.deleteKey(tag) + } + + public static func encrypt(_ message: String, tag: KeyTag) throws -> SEEncryptedData { + let key = try generateRandomBytes(count: 32) + let iv = try generateRandomBytes(count: 16) + + let encryptedKey = try publicEncrypt(data: key, keyForTag: tag) + let encryptedIv = try publicEncrypt(data: iv, keyForTag: tag) + + return SEEncryptedData( + data: try AesCipher.encrypt(message: message, key: key, iv: iv), + key: encryptedKey.base64EncodedString(), + iv: encryptedIv.base64EncodedString() + ) + } + + public static func decrypt(_ encryptedData: SEEncryptedData, tag: KeyTag) throws -> String { + let privateKey = try SecKeyHelper.obtainKey(for: tag.privateTag) + + return try decrypt(encryptedData, privateKey: privateKey) + } + + public static func decrypt(_ encryptedData: SEEncryptedData, privateKey: SecKey) throws -> String { + let decryptedKey = try privateDecrypt(message: encryptedData.key, privateKey: privateKey) + let decryptedIv = try privateDecrypt(message: encryptedData.iv, privateKey: privateKey) + + return try AesCipher.decrypt(data: encryptedData.data, key: decryptedKey, iv: decryptedIv) + } + + public static func publicKeyData(for tag: KeyTag) throws -> Data { + return try SecKeyHelper.obtainKeyData(for: tag) + } + + public static func privateKey(for tag: KeyTag) throws -> SecKey { + return try SecKeyHelper.obtainKey(for: tag.privateTag) + } + + // MARK: - Private Methods + private static func privateDecrypt(message: String, privateKey: SecKey) throws -> Data { + guard let data = Data(base64Encoded: message.replacingOccurrences(of: "\n", with: "")) else { + throw SECryptoHelperError.errorCreatingData(fromBase64: message) + } + + let blockSize = SecKeyGetBlockSize(privateKey) + + var encryptedDataAsArray = [UInt8](repeating: 0, count: data.count) + (data as NSData).getBytes(&encryptedDataAsArray, length: data.count) + + var decryptedDataBytes = [UInt8](repeating: 0, count: 0) + var idx = 0 + while idx < encryptedDataAsArray.count { + + let idxEnd = min(idx + blockSize, encryptedDataAsArray.count) + let chunkData = [UInt8](encryptedDataAsArray[idx..(decryptedDataBytes), count: decryptedDataBytes.count) + } + + private static func publicEncrypt(data: Data, keyForTag: KeyTag) throws -> Data { + let publicKey = try SecKeyHelper.obtainKey(for: keyForTag) + + let blockSize = SecKeyGetBlockSize(publicKey) + let maxChunkSize = blockSize - 11 // Since PKCS1 padding is used + + var decryptedDataAsArray = [UInt8](repeating: 0, count: data.count) + (data as NSData).getBytes(&decryptedDataAsArray, length: data.count) + + var encryptedDataBytes = [UInt8](repeating: 0, count: 0) + var idx = 0 + while idx < decryptedDataAsArray.count { + + let idxEnd = min(idx + maxChunkSize, decryptedDataAsArray.count) + let chunkData = [UInt8](decryptedDataAsArray[idx..(encryptedDataBytes), count: encryptedDataBytes.count) + } + + private static func generateRandomBytes(count: Int) throws -> Data { + let keyData = Data(count: count) + var newData = keyData + let result = newData.withUnsafeMutableBytes { SecRandomCopyBytes(kSecRandomDefault, keyData.count, $0.baseAddress!) } + if result == errSecSuccess { + return keyData + } else { + throw SECryptoHelperError.errorGeneratingRandomBytes + } + } +} + +private struct AesCipher { + static func encrypt(message: String, key: Data, iv: Data) throws -> String { + guard let data = message.data(using: .utf8) else { + throw SEAesCipherError.couldNotCreateData(from: message) + } + + let keyArray = [UInt8](key) + let ivArray = [UInt8](iv) + + do { + let enc = try AES(key: keyArray, blockMode: CBC(iv: ivArray)).encrypt(data.bytes) + let encData = NSData(bytes: enc, length: Int(enc.count)) + let base64String: String = encData.base64EncodedString(options: []) + + return String(base64String) + } catch { + throw error + } + } + + static func decrypt(data: String, key: Data, iv: Data) throws -> String { + guard let encryptedData = Data(base64Encoded: data, options: [.ignoreUnknownCharacters]) else { + throw SEAesCipherError.couldNotCreateEncryptedData(fromBase64: data) + } + + let keyArray = [UInt8](key) + let ivArray = [UInt8](iv) + + if keyArray.isEmpty { throw SEAesCipherError.noKeyProvided } + if iv.isEmpty { throw SEAesCipherError.noIvProvided } + do { + let dec = try AES(key: keyArray, blockMode: CBC(iv: ivArray)).decrypt(encryptedData.bytes) + let decData = NSData(bytes: dec, length: Int(dec.count)) + + guard let result = String(data: decData as Data, encoding: .utf8) else { + throw SEAesCipherError.couldNotCreateDecodedString(fromData: decData as Data) + } + + return String(result) + } catch { + throw error + } + } +} + +private struct SecKeyHelper { + static func generateKeyPair(tag: KeyTag) -> KeyPair? { + let privateAttributes = [ + String(kSecAttrIsPermanent): true, + String(kSecAttrApplicationTag): tag.privateTag, + String(kSecAttrAccessible): kSecAttrAccessibleWhenUnlockedThisDeviceOnly + ] as [String: Any] + + let publicAttributes = [ + String(kSecAttrIsPermanent): true, + String(kSecAttrApplicationTag): tag, + String(kSecAttrAccessible): kSecAttrAccessibleWhenUnlockedThisDeviceOnly + ] as [String: Any] + + let pairAttributes = [ + String(kSecAttrKeyType): kSecAttrKeyTypeRSA, + String(kSecAttrKeySizeInBits): 2048 as UInt, + String(kSecPublicKeyAttrs): publicAttributes, + String(kSecPrivateKeyAttrs): privateAttributes + ] as [String: Any] + + var publicRef, privateRef: SecKey? + switch SecKeyGeneratePair(pairAttributes as CFDictionary, &publicRef, &privateRef) { + case noErr: + if let publicKey = publicRef, let privateKey = privateRef { + return (publicKey, privateKey) + } + default: break + } + return nil + } + + static func createKey(fromFile name: String, isPublic: Bool, tag: String) throws -> SecKey { + let base64Encoded = base64String(pemEncoded: pem(name)) + + guard let data = Data(base64Encoded: base64Encoded, options: [.ignoreUnknownCharacters]) else { + throw SECryptoHelperError.errorCreatingData(fromBase64: base64Encoded) + } + + let keyClass = isPublic ? kSecAttrKeyClassPublic : kSecAttrKeyClassPrivate + + let persistKey = UnsafeMutablePointer(mutating: nil) + + let keyAddDict: [String: Any] = [ + String(kSecClass): kSecClassKey, + String(kSecAttrApplicationTag): tag, + String(kSecAttrKeyType): kSecAttrKeyTypeRSA, + String(kSecValueData): data as CFData, + String(kSecAttrKeyClass): keyClass, + String(kSecReturnPersistentRef): true as CFBoolean, + String(kSecAttrAccessible): kSecAttrAccessibleWhenUnlockedThisDeviceOnly + ] + + let secStatus = SecItemAdd(keyAddDict as CFDictionary, persistKey) + guard secStatus == errSecSuccess || secStatus == errSecDuplicateItem else { + throw SESecKeyHelperError.couldNotAddToKeychain + } + + return try obtainKey(for: tag) + } + + static func obtainKeyData(for tag: KeyTag) throws -> Data { + var keyRef: AnyObject? + let query: [String: AnyObject] = [ + String(kSecAttrKeyType): kSecAttrKeyTypeRSA, + String(kSecReturnData): kCFBooleanTrue as CFBoolean, + String(kSecClass): kSecClassKey as CFString, + String(kSecAttrApplicationTag): tag as CFString + ] + + switch SecItemCopyMatching(query as CFDictionary, &keyRef) { + case noErr: + guard let ref = keyRef as? Data else { throw SESecKeyHelperError.couldNotObtainKeyData(for: tag) } + + return ref + default: + throw SESecKeyHelperError.couldNotObtainKeyData(for: tag) + } + } + + static func obtainKey(for tag: KeyTag) throws -> SecKey { + var keyRef: AnyObject? + let query: [String: AnyObject] = [ + String(kSecAttrKeyType): kSecAttrKeyTypeRSA, + String(kSecReturnRef): kCFBooleanTrue as CFBoolean, + String(kSecClass): kSecClassKey as CFString, + String(kSecAttrApplicationTag): tag as CFString + ] + + switch SecItemCopyMatching(query as CFDictionary, &keyRef) { + case noErr: + guard let ref = keyRef else { throw SESecKeyHelperError.couldNotObtainKey(for: tag) } + + // swiftlint:disable:next force_cast + return (ref as! SecKey) + default: + throw SESecKeyHelperError.couldNotObtainKey(for: tag) + } + } + + @discardableResult + static func deleteKey(_ tag: KeyTag) -> Bool { + let query: [String: AnyObject] = [ + String(kSecAttrKeyType): kSecAttrKeyTypeRSA, + String(kSecClass): kSecClassKey as CFString, + String(kSecAttrApplicationTag): tag as CFString + ] + + return SecItemDelete(query as CFDictionary) == noErr + } + + static func base64String(pemEncoded pemString: String) -> String { + return pemString.components(separatedBy: "\n").filter { line in + return !line.hasPrefix("-----BEGIN") && !line.hasPrefix("-----END") + }.joined(separator: "") + } + + static func pem(_ filename: String) -> String { + let path = Bundle.main.path(forResource: filename, ofType: "pem")! + // swiftlint:disable:next force_try + let pemString = try! String(contentsOfFile: path, encoding: .utf8) + return pemString + } +} + +// MARK: - Public Extensions +extension Data { + var string: String { + let beginPublicKey = "-----BEGIN PUBLIC KEY-----\n" + let endPublicKey = "\n-----END PUBLIC KEY-----\n" + let base64PublicKey = dataByPrependingX509Header() + .base64EncodedString(options: [.lineLength64Characters, .endLineWithLineFeed]) + + return (beginPublicKey + base64PublicKey + endPublicKey) + } +} + +// MARK: - Private Extensions +extension Data { + func dataByPrependingX509Header() -> Data { + let result = NSMutableData() + + let encodingLength: Int = (self.count + 1).encodedOctets().count + let OID: [CUnsignedChar] = [0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00] + + var builder: [CUnsignedChar] = [] + + // ASN.1 SEQUENCE + builder.append(0x30) + + // Overall size, made of OID + bitstring encoding + actual key + let size = OID.count + 2 + encodingLength + self.count + let encodedSize = size.encodedOctets() + builder.append(contentsOf: encodedSize) + result.append(builder, length: builder.count) + result.append(OID, length: OID.count) + builder.removeAll(keepingCapacity: false) + + builder.append(0x03) + builder.append(contentsOf: (self.count + 1).encodedOctets()) + builder.append(0x00) + result.append(builder, length: builder.count) + + // Actual key bytes + result.append(self) + + return result as Data + } +} + +extension NSInteger { + func encodedOctets() -> [CUnsignedChar] { + // Short form + if self < 128 { + return [CUnsignedChar(self)] + } + + // Long form + let i = Int(log2(Double(self)) / 8 + 1) + var len = self + var result: [CUnsignedChar] = [CUnsignedChar(i + 0x80)] + + for _ in 0..> 8 + } + + return result + } +} + +private extension KeyTag { + var privateTag: KeyTag { + return self + ".private" + } +} diff --git a/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelperError.swift b/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelperError.swift new file mode 100644 index 00000000..7f63852d --- /dev/null +++ b/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelperError.swift @@ -0,0 +1,82 @@ +// +// SECryptoHelperError.swift +// SaltedgeAuthenticatorSDK +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright (c) 2019 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md + + +import Foundation + +enum SECryptoHelperError: Error { + case errorGeneratingRandomBytes + case errorCreatingData(fromBase64: String) + case couldNotEncryptChunk(at: Int) + case couldNotDecryptChunk(at: Int) + + var localizedDescription: String { + var message = "" + switch self { + case .errorCreatingData(let string): + message = "from base 64 encoded \(string)" + case .couldNotEncryptChunk(let index): + message = "at index: \(index)" + case .couldNotDecryptChunk(let index): + message = "at index: \(index)" + default: + break + } + return "\(String(describing: type(of: self))).\(String(describing: self)) \(message)" + } +} + +enum SEAesCipherError: Error { + case couldNotCreateData(from: String) + case couldNotCreateString(fromBase64: String) + case couldNotCreateEncryptedData(fromBase64: String) + case couldNotCreateDecodedString(fromData: Data) + case noKeyProvided + case noIvProvided + + var localizedDescription: String { + var message = "" + switch self { + case .couldNotCreateData(let string), + .couldNotCreateString(let string), + .couldNotCreateEncryptedData(let string): + message = string + case .couldNotCreateDecodedString(let data): + message = data.base64EncodedString() + default: + break + } + + return "\(String(describing: type(of: self))).\(String(describing: self)) \(message)" + } +} + +enum SESecKeyHelperError: Error { + case couldNotObtainKey(for: String) + case couldNotObtainKeyData(for: String) + case couldNotAddToKeychain + + var localizedDescription: String { + return "\(String(describing: type(of: self))).\(String(describing: self))" + } +} + diff --git a/SaltedgeAuthenticatorCore/Classes/Crypto/SETagHelper.swift b/SaltedgeAuthenticatorCore/Classes/Crypto/SETagHelper.swift new file mode 100644 index 00000000..4e422164 --- /dev/null +++ b/SaltedgeAuthenticatorCore/Classes/Crypto/SETagHelper.swift @@ -0,0 +1,29 @@ +// +// SETagHelper.swift +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2019 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation + +public struct SETagHelper { + public static func create(for code: String) -> String { + return "com.saltedge.authenticator.\(code)" + } +} diff --git a/SaltedgeAuthenticatorCore/Classes/DictionaryExtensions.swift b/SaltedgeAuthenticatorCore/Classes/DictionaryExtensions.swift new file mode 100644 index 00000000..bc0928b8 --- /dev/null +++ b/SaltedgeAuthenticatorCore/Classes/DictionaryExtensions.swift @@ -0,0 +1,47 @@ +// +// DictionaryExtensions.swift +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation + +public extension Dictionary { + func merge(with other: Dictionary) -> Dictionary { + var copy = self + for (k, v) in other { + copy.updateValue(v, forKey: k) + } + return copy + } + + var jsonString: String? { + if let data = try? JSONSerialization.data(withJSONObject: self, options: []), + let string = String(data: data, encoding: String.Encoding.utf8) { + return string + } + return nil + } +} + +extension Dictionary where Key: Hashable, Value: Any { + static func == (lhs: [Key: Value], rhs: [Key: Value]) -> Bool { + return NSDictionary(dictionary: lhs).isEqual(to: rhs) + } +} diff --git a/SaltedgeAuthenticatorCore/Classes/Helpers/ParametersSerializer.swift b/SaltedgeAuthenticatorCore/Classes/Helpers/ParametersSerializer.swift new file mode 100644 index 00000000..a4721301 --- /dev/null +++ b/SaltedgeAuthenticatorCore/Classes/Helpers/ParametersSerializer.swift @@ -0,0 +1,42 @@ +// +// ParametersSerializer.swift +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation + +struct ParametersSerializer { + static func createBody(parameters: [String: Any]) -> Data? { + do { + var data: Data + if #available(iOS 11.0, *) { + data = try JSONSerialization.data(withJSONObject: parameters, options: [.sortedKeys, .prettyPrinted]) + } else { + data = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted) + } + + return data + } catch { + print(error.localizedDescription) + } + + return nil + } +} diff --git a/SaltedgeAuthenticatorCore/Classes/Helpers/TypeAliases.swift b/SaltedgeAuthenticatorCore/Classes/Helpers/TypeAliases.swift new file mode 100644 index 00000000..19e1f2ed --- /dev/null +++ b/SaltedgeAuthenticatorCore/Classes/Helpers/TypeAliases.swift @@ -0,0 +1,34 @@ +// +// TypeAliases.swift +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +public typealias SuccessBlock = () -> () +public typealias FailureBlock = (String) -> () +public typealias RequestSuccessBlock = ([String: Any]?) -> () +public typealias HTTPServiceSuccessClosure = (T) -> () + +public typealias AccessToken = String +public typealias GUID = String +public typealias ID = String + +public typealias PushToken = String +public typealias ConnectQuery = String +public typealias ApplicationLanguage = String diff --git a/SaltedgeAuthenticatorCore/Classes/Routers/Routable.swift b/SaltedgeAuthenticatorCore/Classes/Routers/Routable.swift new file mode 100644 index 00000000..09782120 --- /dev/null +++ b/SaltedgeAuthenticatorCore/Classes/Routers/Routable.swift @@ -0,0 +1,73 @@ +// +// Routable.swift +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation + +public enum HTTPMethod: String { + case get = "GET" + case post = "POST" + case put = "PUT" + case delete = "DELETE" +} + +public enum Encoding: String { + case url + case json +} + +public protocol Routable { + var method: HTTPMethod { get } + var encoding: Encoding { get } + var url: URL { get } + var headers: [String: String]? { get } + var parameters: [String: Any]? { get } +} + +public extension Routable { + func asURLRequest() -> URLRequest { + var request = URLRequest(url: url) + request.httpMethod = method.rawValue + request.allHTTPHeaderFields = headers + + guard let parameters = parameters else { return request } + + if encoding == .url { + var components = URLComponents(url: url, resolvingAgainstBaseURL: true) + components?.queryItems = parameters.asUrlQueryItems() + request.url = components?.url + } + + if request.value(forHTTPHeaderField: HeadersKeys.contentType) == nil { + request.setValue("application/json", forHTTPHeaderField: HeadersKeys.contentType) + } + + request.httpBody = ParametersSerializer.createBody(parameters: parameters) + + return request + } +} + +private extension Dictionary { + func asUrlQueryItems() -> [URLQueryItem] { + return map { URLQueryItem(name: "\($0.0)", value: "\($0.1)") } + } +} diff --git a/SaltedgeAuthenticatorCore/Classes/SEWebView/SEWebView.swift b/SaltedgeAuthenticatorCore/Classes/SEWebView/SEWebView.swift new file mode 100644 index 00000000..c07cecda --- /dev/null +++ b/SaltedgeAuthenticatorCore/Classes/SEWebView/SEWebView.swift @@ -0,0 +1,87 @@ +// +// SEWebView.swift +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import WebKit + +public protocol SEWebViewDelegate: NSObjectProtocol { + func webView(_ webView: WKWebView, didReceiveCallback url: URL, accessToken: AccessToken) + func webView(_ webView: WKWebView, didReceiveCallbackWithError error: String?) + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) +} + +public class SEWebView: WKWebView { + public weak var delegate: SEWebViewDelegate? + + public init(frame: CGRect) { + super.init(frame: frame, configuration: WKWebViewConfiguration()) + self.navigationDelegate = self + } + + public required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + self.navigationDelegate = self + } +} + +// MARK: - WKNavigationDelegate +extension SEWebView: WKNavigationDelegate { + public func webView(_ webView: WKWebView, + decidePolicyFor navigationAction: WKNavigationAction, + decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { + if let url = navigationAction.request.url, SENetConstants.hasRedirectUrl(url.absoluteString) { + if let accessToken = url.queryItem(for: SENetKeys.accessToken) { + self.delegate?.webView(self, didReceiveCallback: url, accessToken: accessToken) + } else { + let error: String = url.queryItem(for: SENetKeys.errorMessage) + ?? url.queryItem(for: SENetKeys.errorClass) + ?? "Something went wrong" + + self.delegate?.webView(self, didReceiveCallbackWithError: error) + } + + decisionHandler(.cancel) + } else { + decisionHandler(.allow) + } + } + + public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + delegate?.webView(webView, didFinish: navigation) + } + + public func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { + delegate?.webView(webView, didReceiveCallbackWithError: error.localizedDescription) + } + + public func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { + let error = (error as NSError) + + if error.domain == "WebKitErrorDomain" && error.code == 102 { + // Error code 102 "Frame load interrupted" is raised by the WKWebView + // when the URL is from an http redirect. This is a common pattern when + // implementing OAuth with a WebView. + return + } else { + delegate?.webView(webView, didReceiveCallbackWithError: error.localizedDescription) + } + } +} diff --git a/SaltedgeAuthenticatorCore/Classes/URLExtensions.swift b/SaltedgeAuthenticatorCore/Classes/URLExtensions.swift new file mode 100644 index 00000000..565ffade --- /dev/null +++ b/SaltedgeAuthenticatorCore/Classes/URLExtensions.swift @@ -0,0 +1,31 @@ +// +// URLExtensions.swift +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation + +extension URL { + func queryItem(for key: String) -> String? { + guard let components = URLComponents(url: self, resolvingAgainstBaseURL: false) else { return nil } + + return components.queryItems?.first(where: { $0.name == key })?.value + } +} diff --git a/SaltedgeAuthenticatorCore/SaltedgeAuthenticatorCore.podspec b/SaltedgeAuthenticatorCore/SaltedgeAuthenticatorCore.podspec new file mode 100644 index 00000000..e46a2a86 --- /dev/null +++ b/SaltedgeAuthenticatorCore/SaltedgeAuthenticatorCore.podspec @@ -0,0 +1,31 @@ +# +# Be sure to run `pod lib lint SaltedgeAuthenticatorSDK.podspec' to ensure this is a +# valid spec before submitting. +# +# Any lines starting with a # are optional, but their use is encouraged +# To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html +# + +Pod::Spec.new do |s| + s.name = 'SaltedgeAuthenticatorCore' + s.version = '1.1.0' + s.summary = 'SDK for decoupled authentication solution to meet the requirements of Strong Customer Authentication (SCA)' + + s.description = <<-DESC + Authenticator iOS SDK - is a module for connecting to Salt Edge Authenticator API of + Bank (Service Provider) System, that implements Strong Customer Authentication/Dynamic Linking process. + DESC + + s.homepage = 'https://github.com/saltedge/sca-authenticator-ios' + s.license = { :type => 'GPLv3', :file => '../LICENSE.txt' } + s.author = { 'Salt Edge Inc.' => 'authenticator@saltedge.com' } + s.source = { :git => 'https://github.com/saltedge/sca-authenticator-ios.git', :tag => s.version.to_s } + + s.ios.deployment_target = '10.0' + s.swift_version = '5' + s.module_name = 'SEAuthenticatorCore' + + s.source_files = '**/Classes/**/*' + + s.dependency 'CryptoSwift' +end diff --git a/SaltedgeAuthenticatorSDK.podspec b/SaltedgeAuthenticatorSDK.podspec index a49b7694..b5269aa0 100644 --- a/SaltedgeAuthenticatorSDK.podspec +++ b/SaltedgeAuthenticatorSDK.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'SaltedgeAuthenticatorSDK' - s.version = '1.1.1' + s.version = '1.1.0' s.summary = 'SDK for decoupled authentication solution to meet the requirements of Strong Customer Authentication (SCA)' s.description = <<-DESC @@ -27,5 +27,6 @@ Pod::Spec.new do |s| s.source_files = 'SaltedgeAuthenticatorSDK/Classes/**/*' + s.dependency 'SaltedgeAuthenticatorCore' s.dependency 'CryptoSwift' end diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEActionRouter.swift b/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEActionRouter.swift index e047b6ac..f3f0e979 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEActionRouter.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEActionRouter.swift @@ -21,6 +21,7 @@ // import Foundation +//import SEAuthenticatorCore enum SEActionRouter: Routable { case submit(SEActionRequestData) diff --git a/SaltedgeAuthenticatorSDK/Classes/SEWebView/SEWebView.swift b/SaltedgeAuthenticatorSDK/Classes/SEWebView/SEWebView.swift index e9fd4111..3631ddbd 100644 --- a/SaltedgeAuthenticatorSDK/Classes/SEWebView/SEWebView.swift +++ b/SaltedgeAuthenticatorSDK/Classes/SEWebView/SEWebView.swift @@ -20,68 +20,68 @@ // under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md // -import WebKit - -public protocol SEWebViewDelegate: NSObjectProtocol { - func webView(_ webView: WKWebView, didReceiveCallback url: URL, accessToken: AccessToken) - func webView(_ webView: WKWebView, didReceiveCallbackWithError error: String?) - func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) -} - -public class SEWebView: WKWebView { - public weak var delegate: SEWebViewDelegate? - - public init(frame: CGRect) { - super.init(frame: frame, configuration: WKWebViewConfiguration()) - self.navigationDelegate = self - } - - public required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - self.navigationDelegate = self - } -} - -// MARK: - WKNavigationDelegate -extension SEWebView: WKNavigationDelegate { - public func webView(_ webView: WKWebView, - decidePolicyFor navigationAction: WKNavigationAction, - decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { - if let url = navigationAction.request.url, SENetConstants.hasRedirectUrl(url.absoluteString) { - if let accessToken = url.queryItem(for: SENetKeys.accessToken) { - self.delegate?.webView(self, didReceiveCallback: url, accessToken: accessToken) - } else { - let error: String = url.queryItem(for: SENetKeys.errorMessage) - ?? url.queryItem(for: SENetKeys.errorClass) - ?? "Something went wrong" - - self.delegate?.webView(self, didReceiveCallbackWithError: error) - } - - decisionHandler(.cancel) - } else { - decisionHandler(.allow) - } - } - - public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { - delegate?.webView(webView, didFinish: navigation) - } - - public func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { - delegate?.webView(webView, didReceiveCallbackWithError: error.localizedDescription) - } - - public func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { - let error = (error as NSError) - - if error.domain == "WebKitErrorDomain" && error.code == 102 { - // Error code 102 "Frame load interrupted" is raised by the WKWebView - // when the URL is from an http redirect. This is a common pattern when - // implementing OAuth with a WebView. - return - } else { - delegate?.webView(webView, didReceiveCallbackWithError: error.localizedDescription) - } - } -} +//import WebKit +// +//public protocol SEWebViewDelegate: NSObjectProtocol { +// func webView(_ webView: WKWebView, didReceiveCallback url: URL, accessToken: AccessToken) +// func webView(_ webView: WKWebView, didReceiveCallbackWithError error: String?) +// func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) +//} +// +//public class SEWebView: WKWebView { +// public weak var delegate: SEWebViewDelegate? +// +// public init(frame: CGRect) { +// super.init(frame: frame, configuration: WKWebViewConfiguration()) +// self.navigationDelegate = self +// } +// +// public required init?(coder aDecoder: NSCoder) { +// super.init(coder: aDecoder) +// self.navigationDelegate = self +// } +//} +// +//// MARK: - WKNavigationDelegate +//extension SEWebView: WKNavigationDelegate { +// public func webView(_ webView: WKWebView, +// decidePolicyFor navigationAction: WKNavigationAction, +// decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { +// if let url = navigationAction.request.url, SENetConstants.hasRedirectUrl(url.absoluteString) { +// if let accessToken = url.queryItem(for: SENetKeys.accessToken) { +// self.delegate?.webView(self, didReceiveCallback: url, accessToken: accessToken) +// } else { +// let error: String = url.queryItem(for: SENetKeys.errorMessage) +// ?? url.queryItem(for: SENetKeys.errorClass) +// ?? "Something went wrong" +// +// self.delegate?.webView(self, didReceiveCallbackWithError: error) +// } +// +// decisionHandler(.cancel) +// } else { +// decisionHandler(.allow) +// } +// } +// +// public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { +// delegate?.webView(webView, didFinish: navigation) +// } +// +// public func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { +// delegate?.webView(webView, didReceiveCallbackWithError: error.localizedDescription) +// } +// +// public func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { +// let error = (error as NSError) +// +// if error.domain == "WebKitErrorDomain" && error.code == 102 { +// // Error code 102 "Frame load interrupted" is raised by the WKWebView +// // when the URL is from an http redirect. This is a common pattern when +// // implementing OAuth with a WebView. +// return +// } else { +// delegate?.webView(webView, didReceiveCallbackWithError: error.localizedDescription) +// } +// } +//} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift b/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift new file mode 100644 index 00000000..72a89ccb --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift @@ -0,0 +1,23 @@ +// +// RequestParametersBuilder +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation diff --git a/SaltedgeAuthenticatorSDKv2/Classes/Routers/SEConnectionRouter.swift b/SaltedgeAuthenticatorSDKv2/Classes/Routers/SEConnectionRouter.swift new file mode 100644 index 00000000..a6b939d5 --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/Routers/SEConnectionRouter.swift @@ -0,0 +1,94 @@ +// +// SEConnectionRouter.swift +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +public struct SECreateConnectionParams { + public let providerId: String + public let returnUrl: String + public let platform: String + public let pushToken: String? + public let connectQuery: String? + public let encryptedRsaPublicKey: SEEncryptedData +} + +enum SEConnectionRouter: Routable { + case createConnection(URL, SECreateConnectionParams, String) +// case revoke(SEBaseAuthenticatedWithIdRequestData) + + var method: HTTPMethod { + switch self { + case .createConnection: return .post +// case .revoke: return .delete + } + } + + var encoding: Encoding { + switch self { + case .createConnection: return .json +// case .revoke: return .url + } + } + + var url: URL { + switch self { + case .createConnection(let connectUrl, _, _): return connectUrl +// case .revoke(let data): return data.url.appendingPathComponent("\(SENetPaths.connections.path)") + } + } + + var headers: [String: String]? { + switch self { + case .createConnection(_, _, let appLanguage): return Headers.requestHeaders(with: appLanguage) +// case .revoke(let data): +// let expiresAt = Date().addingTimeInterval(5.0 * 60.0).utcSeconds +// +// let signature = SignatureHelper.signedPayload( +// method: .delete, +// urlString: url.absoluteString, +// guid: data.connectionGuid, +// expiresAt: expiresAt, +// params: parameters +// ) +// +// return Headers.signedRequestHeaders( +// token: data.accessToken, +// expiresAt: expiresAt, +// signature: signature, +// appLanguage: data.appLanguage +// ) + } + } + + var parameters: [String: Any]? { + switch self { + case .createConnection(_, let data, _): + return RequestParametersBuilder.parameters( + for: data, + pushToken: pushToken, + connectQuery: connectQuery + ) +// case .revoke: return nil + } + } +} diff --git a/SaltedgeAuthenticatorSDKv2/SaltedgeAuthenticatorSDKv2.podspec b/SaltedgeAuthenticatorSDKv2/SaltedgeAuthenticatorSDKv2.podspec new file mode 100644 index 00000000..0c7ce6d2 --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/SaltedgeAuthenticatorSDKv2.podspec @@ -0,0 +1,32 @@ +# +# Be sure to run `pod lib lint SaltedgeAuthenticatorSDK.podspec' to ensure this is a +# valid spec before submitting. +# +# Any lines starting with a # are optional, but their use is encouraged +# To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html +# + +Pod::Spec.new do |s| + s.name = 'SaltedgeAuthenticatorSDKv2' + s.version = '1.1.0' + s.summary = 'SDK for decoupled authentication solution to meet the requirements of Strong Customer Authentication (SCA)' + + s.description = <<-DESC + Authenticator iOS SDK - is a module for connecting to Salt Edge Authenticator API of + Bank (Service Provider) System, that implements Strong Customer Authentication/Dynamic Linking process. + DESC + + s.homepage = 'https://github.com/saltedge/sca-authenticator-ios' + s.license = { :type => 'GPLv3', :file => '../LICENSE.txt' } + s.author = { 'Salt Edge Inc.' => 'authenticator@saltedge.com' } + s.source = { :git => 'https://github.com/saltedge/sca-authenticator-ios.git', :tag => s.version.to_s } + + s.ios.deployment_target = '10.0' + s.swift_version = '5' + s.module_name = 'SEAuthenticatorV2' + + s.source_files = '**/Classes/**/*' + + s.dependency 'SaltedgeAuthenticatorCore' + s.dependency 'CryptoSwift' +end From a26489a0ca31ff8144282435ecd35f38c538be57 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Mon, 17 May 2021 15:58:27 +0300 Subject: [PATCH 002/110] remade net paths, enriched SEConnectionRouter in v2 --- .../Interactors/ConnectionsInteractor.swift | 2 +- .../Classes/API/SENetConstants.swift | 6 +-- .../Classes/API/SENetPathBuilder.swift | 18 ++++--- .../API/RequestParametersBuilder.swift | 1 + .../Classes/API/Routers/SEActionRouter.swift | 8 +-- .../API/Routers/SEAuthorizationRouter.swift | 11 +++-- .../API/Routers/SEConnectionRouter.swift | 7 ++- .../API/Routers/SEConsentsRouter.swift | 5 +- .../Classes/API/SENetPaths.swift | 38 -------------- .../Classes/RequestParametersBuilder.swift | 49 +++++++++++++++++++ .../Classes/Routers/SEConnectionRouter.swift | 29 +---------- 11 files changed, 86 insertions(+), 88 deletions(-) rename SaltedgeAuthenticatorSDK/Classes/API/SENetConstants.swift => SaltedgeAuthenticatorCore/Classes/API/SENetPathBuilder.swift (70%) delete mode 100644 SaltedgeAuthenticatorSDK/Classes/API/SENetPaths.swift diff --git a/Example/Authenticator/Utils/Interactors/ConnectionsInteractor.swift b/Example/Authenticator/Utils/Interactors/ConnectionsInteractor.swift index 3f7f3d20..71bcd404 100644 --- a/Example/Authenticator/Utils/Interactors/ConnectionsInteractor.swift +++ b/Example/Authenticator/Utils/Interactors/ConnectionsInteractor.swift @@ -80,7 +80,7 @@ struct ConnectionsInteractor { failure: @escaping (String) -> () ) { guard let connectionData = SECreateConnectionRequestData(code: connection.code, tag: connection.guid), - let connectUrl = connection.baseUrl?.appendingPathComponent(SENetPaths.connections.path) else { return } + let connectUrl = connection.baseUrl else { return } SEConnectionManager.createConnection( by: connectUrl, diff --git a/SaltedgeAuthenticatorCore/Classes/API/SENetConstants.swift b/SaltedgeAuthenticatorCore/Classes/API/SENetConstants.swift index 46f39a90..3202b266 100644 --- a/SaltedgeAuthenticatorCore/Classes/API/SENetConstants.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/SENetConstants.swift @@ -22,12 +22,12 @@ import Foundation -struct SENetConstants { - static var oauthRedirectUrl: String { +public struct SENetConstants { + public static var oauthRedirectUrl: String { return "authenticator://oauth/redirect" } - static func hasRedirectUrl(_ urlString: String) -> Bool { + public static func hasRedirectUrl(_ urlString: String) -> Bool { return urlString.starts(with: oauthRedirectUrl) } } diff --git a/SaltedgeAuthenticatorSDK/Classes/API/SENetConstants.swift b/SaltedgeAuthenticatorCore/Classes/API/SENetPathBuilder.swift similarity index 70% rename from SaltedgeAuthenticatorSDK/Classes/API/SENetConstants.swift rename to SaltedgeAuthenticatorCore/Classes/API/SENetPathBuilder.swift index 3dc67cfe..c7c1e45e 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/SENetConstants.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/SENetPathBuilder.swift @@ -1,8 +1,8 @@ // -// SENetConstants.swift +// SENetPathBuilder // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -22,12 +22,16 @@ import Foundation -struct SENetConstants { - static var oauthRedirectUrl: String { - return "authenticator://oauth/redirect" +public struct SENetPathBuilder { + public enum Endpoints: String { + case connections + case authorizations + case actions + case consents } + public var path: String - static func hasRedirectUrl(_ urlString: String) -> Bool { - return urlString.starts(with: oauthRedirectUrl) + public init(for endpoint: Endpoints, version: Int = 1) { + path = "/api/authenticator/v\(version)/\(endpoint.rawValue)" } } diff --git a/SaltedgeAuthenticatorSDK/Classes/API/RequestParametersBuilder.swift b/SaltedgeAuthenticatorSDK/Classes/API/RequestParametersBuilder.swift index 917cc7dc..413b93fc 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/RequestParametersBuilder.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/RequestParametersBuilder.swift @@ -21,6 +21,7 @@ // import Foundation +import SEAuthenticatorCore struct ParametersKeys { static let data = "data" diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEActionRouter.swift b/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEActionRouter.swift index f3f0e979..2f503cf8 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEActionRouter.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEActionRouter.swift @@ -21,7 +21,7 @@ // import Foundation -//import SEAuthenticatorCore +import SEAuthenticatorCore enum SEActionRouter: Routable { case submit(SEActionRequestData) @@ -37,7 +37,7 @@ enum SEActionRouter: Routable { var url: URL { switch self { case .submit(let data): - return data.url.appendingPathComponent("\(SENetPaths.actions.path)/\(data.guid)") + return data.url.appendingPathComponent("\(SENetPathBuilder(for: .actions).path)/\(data.guid)") } } @@ -48,7 +48,9 @@ enum SEActionRouter: Routable { let signature = SignatureHelper.signedPayload( method: .put, - urlString: data.url.appendingPathComponent("\(SENetPaths.actions.path)/\(data.guid)").absoluteString, + urlString: data.url.appendingPathComponent( + "\(SENetPathBuilder(for: .actions).path)/\(data.guid)" + ).absoluteString, guid: data.connectionGuid, expiresAt: expiresAt, params: parameters diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEAuthorizationRouter.swift b/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEAuthorizationRouter.swift index c977f1f9..f4da208c 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEAuthorizationRouter.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEAuthorizationRouter.swift @@ -21,6 +21,7 @@ // import Foundation +import SEAuthenticatorCore enum SEAuthorizationRouter: Routable { case list(SEBaseAuthenticatedRequestData) @@ -45,11 +46,11 @@ enum SEAuthorizationRouter: Routable { var url: URL { switch self { case .list(let data): - return data.url.appendingPathComponent(SENetPaths.authorizations.path) + return data.url.appendingPathComponent(SENetPathBuilder(for: .authorizations).path) case .getAuthorization(let data): - return data.url.appendingPathComponent("\(SENetPaths.authorizations.path)/\(data.entityId)") + return data.url.appendingPathComponent("\(SENetPathBuilder(for: .authorizations).path)/\(data.entityId)") case .confirm(let data), .deny(let data): - return data.url.appendingPathComponent("\(SENetPaths.authorizations.path)/\(data.entityId)") + return data.url.appendingPathComponent("\(SENetPathBuilder(for: .authorizations).path)/\(data.entityId)") } } @@ -78,7 +79,7 @@ enum SEAuthorizationRouter: Routable { let signature = SignatureHelper.signedPayload( method: .get, urlString: data.url.appendingPathComponent( - "\(SENetPaths.authorizations.path)/\(data.entityId)" + "\(SENetPathBuilder(for: .authorizations).path)/\(data.entityId)" ).absoluteString, guid: data.connectionGuid, expiresAt: expiresAt, @@ -97,7 +98,7 @@ enum SEAuthorizationRouter: Routable { let signature = SignatureHelper.signedPayload( method: .put, urlString: data.url.appendingPathComponent( - "\(SENetPaths.authorizations.path)/\(data.entityId)" + "\(SENetPathBuilder(for: .authorizations).path)/\(data.entityId)" ).absoluteString, guid: data.connectionGuid, expiresAt: expiresAt, diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEConnectionRouter.swift b/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEConnectionRouter.swift index a8b12e36..e8f64cb5 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEConnectionRouter.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEConnectionRouter.swift @@ -21,6 +21,7 @@ // import Foundation +import SEAuthenticatorCore enum SEConnectionRouter: Routable { case createConnection(URL, SECreateConnectionRequestData, PushToken, ConnectQuery?, ApplicationLanguage) @@ -42,8 +43,10 @@ enum SEConnectionRouter: Routable { var url: URL { switch self { - case .createConnection(let url, _, _, _, _): return url - case .revoke(let data): return data.url.appendingPathComponent("\(SENetPaths.connections.path)") + case .createConnection(let url, _, _, _, _): + return url.appendingPathComponent(SENetPathBuilder(for: .connections).path) + case .revoke(let data): + return data.url.appendingPathComponent(SENetPathBuilder(for: .connections).path) } } diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEConsentsRouter.swift b/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEConsentsRouter.swift index 207545bf..baca59b9 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEConsentsRouter.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEConsentsRouter.swift @@ -21,6 +21,7 @@ // import Foundation +import SEAuthenticatorCore enum SEConsentsRouter: Routable { case list(SEBaseAuthenticatedRequestData) @@ -42,9 +43,9 @@ enum SEConsentsRouter: Routable { var url: URL { switch self { case .list(let data): - return data.url.appendingPathComponent(SENetPaths.consents.path) + return data.url.appendingPathComponent(SENetPathBuilder(for: .consents).path) case .revoke(let data): - return data.url.appendingPathComponent("\(SENetPaths.consents.path)/\(data.entityId)") + return data.url.appendingPathComponent("\(SENetPathBuilder(for: .consents).path)/\(data.entityId)") } } diff --git a/SaltedgeAuthenticatorSDK/Classes/API/SENetPaths.swift b/SaltedgeAuthenticatorSDK/Classes/API/SENetPaths.swift deleted file mode 100644 index 072eb40e..00000000 --- a/SaltedgeAuthenticatorSDK/Classes/API/SENetPaths.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// SENetPaths.swift -// This file is part of the Salt Edge Authenticator distribution -// (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 3 or later. -// -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -// For the additional permissions granted for Salt Edge Authenticator -// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md -// - -import Foundation - -public enum SENetPaths: String { - case connections - case authorizations - case actions - case consents - - public var path: String { - return "/api/authenticator/v\(version)/\(rawValue)" - } - - private var version: Int { - return 1 - } -} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift b/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift index 72a89ccb..a97df866 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift @@ -21,3 +21,52 @@ // import Foundation +import SEAuthenticatorCore + +struct ParametersKeys { + static let data = "data" + static let key = "key" + static let iv = "iv" + static let providerId = "provider_id" + static let publicKey = "public_key" + static let deviceInfo = "device_info" + static let platform = "platform" + static let pushToken = "push_token" + static let returnUrl = "return_url" + static let connectQuery = "connect_query" + static let confirm = "confirm" + static let authorizationCode = "authorization_code" + static let credentials = "credentials" + static let encryptedRsaPublicKey = "encrypted_rsa_public_key" +} + +struct RequestParametersBuilder { + static func parameters(for connectionParams: SECreateConnectionParams) -> [String: Any] { + let encryptedRsaPublicKeyDict: [String: Any] = [ + ParametersKeys.data: connectionParams.encryptedRsaPublicKey.data, + ParametersKeys.key: connectionParams.encryptedRsaPublicKey.key, + ParametersKeys.iv: connectionParams.encryptedRsaPublicKey.iv, + ] + + return [ + ParametersKeys.data: [ + ParametersKeys.providerId: connectionParams.providerId, + ParametersKeys.returnUrl: SENetConstants.oauthRedirectUrl, + ParametersKeys.platform: connectionParams.platform, + ParametersKeys.pushToken: connectionParams.pushToken, + ParametersKeys.encryptedRsaPublicKey: encryptedRsaPublicKeyDict, + ParametersKeys.connectQuery: connectionParams.connectQuery + ] + ] + } + + static func confirmAuthorization(_ confirm: Bool, authorizationCode: String?) -> [String: Any] { + var data: [String: Any] = [ParametersKeys.confirm: confirm] + + if let authorizationCode = authorizationCode { + data = data.merge(with: [ParametersKeys.authorizationCode: authorizationCode]) + } + + return [ParametersKeys.data: data] + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/Routers/SEConnectionRouter.swift b/SaltedgeAuthenticatorSDKv2/Classes/Routers/SEConnectionRouter.swift index a6b939d5..0b805a3c 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/Routers/SEConnectionRouter.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/Routers/SEConnectionRouter.swift @@ -32,63 +32,38 @@ public struct SECreateConnectionParams { public let encryptedRsaPublicKey: SEEncryptedData } +// TODO: Add revoke connection case enum SEConnectionRouter: Routable { case createConnection(URL, SECreateConnectionParams, String) -// case revoke(SEBaseAuthenticatedWithIdRequestData) var method: HTTPMethod { switch self { case .createConnection: return .post -// case .revoke: return .delete } } var encoding: Encoding { switch self { case .createConnection: return .json -// case .revoke: return .url } } var url: URL { switch self { case .createConnection(let connectUrl, _, _): return connectUrl -// case .revoke(let data): return data.url.appendingPathComponent("\(SENetPaths.connections.path)") } } var headers: [String: String]? { switch self { case .createConnection(_, _, let appLanguage): return Headers.requestHeaders(with: appLanguage) -// case .revoke(let data): -// let expiresAt = Date().addingTimeInterval(5.0 * 60.0).utcSeconds -// -// let signature = SignatureHelper.signedPayload( -// method: .delete, -// urlString: url.absoluteString, -// guid: data.connectionGuid, -// expiresAt: expiresAt, -// params: parameters -// ) -// -// return Headers.signedRequestHeaders( -// token: data.accessToken, -// expiresAt: expiresAt, -// signature: signature, -// appLanguage: data.appLanguage -// ) } } var parameters: [String: Any]? { switch self { case .createConnection(_, let data, _): - return RequestParametersBuilder.parameters( - for: data, - pushToken: pushToken, - connectQuery: connectQuery - ) -// case .revoke: return nil + return RequestParametersBuilder.parameters(for: data) } } } From 30b4f80e1a654122f3a1c2a89ab0cae24005ddb0 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Mon, 17 May 2021 16:02:32 +0300 Subject: [PATCH 003/110] removed unneeded file --- .../Classes/SEWebView/SEWebView.swift | 87 ------------------- 1 file changed, 87 deletions(-) delete mode 100644 SaltedgeAuthenticatorSDK/Classes/SEWebView/SEWebView.swift diff --git a/SaltedgeAuthenticatorSDK/Classes/SEWebView/SEWebView.swift b/SaltedgeAuthenticatorSDK/Classes/SEWebView/SEWebView.swift deleted file mode 100644 index 3631ddbd..00000000 --- a/SaltedgeAuthenticatorSDK/Classes/SEWebView/SEWebView.swift +++ /dev/null @@ -1,87 +0,0 @@ -// -// SEWebView.swift -// This file is part of the Salt Edge Authenticator distribution -// (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 3 or later. -// -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -// For the additional permissions granted for Salt Edge Authenticator -// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md -// - -//import WebKit -// -//public protocol SEWebViewDelegate: NSObjectProtocol { -// func webView(_ webView: WKWebView, didReceiveCallback url: URL, accessToken: AccessToken) -// func webView(_ webView: WKWebView, didReceiveCallbackWithError error: String?) -// func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) -//} -// -//public class SEWebView: WKWebView { -// public weak var delegate: SEWebViewDelegate? -// -// public init(frame: CGRect) { -// super.init(frame: frame, configuration: WKWebViewConfiguration()) -// self.navigationDelegate = self -// } -// -// public required init?(coder aDecoder: NSCoder) { -// super.init(coder: aDecoder) -// self.navigationDelegate = self -// } -//} -// -//// MARK: - WKNavigationDelegate -//extension SEWebView: WKNavigationDelegate { -// public func webView(_ webView: WKWebView, -// decidePolicyFor navigationAction: WKNavigationAction, -// decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { -// if let url = navigationAction.request.url, SENetConstants.hasRedirectUrl(url.absoluteString) { -// if let accessToken = url.queryItem(for: SENetKeys.accessToken) { -// self.delegate?.webView(self, didReceiveCallback: url, accessToken: accessToken) -// } else { -// let error: String = url.queryItem(for: SENetKeys.errorMessage) -// ?? url.queryItem(for: SENetKeys.errorClass) -// ?? "Something went wrong" -// -// self.delegate?.webView(self, didReceiveCallbackWithError: error) -// } -// -// decisionHandler(.cancel) -// } else { -// decisionHandler(.allow) -// } -// } -// -// public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { -// delegate?.webView(webView, didFinish: navigation) -// } -// -// public func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { -// delegate?.webView(webView, didReceiveCallbackWithError: error.localizedDescription) -// } -// -// public func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { -// let error = (error as NSError) -// -// if error.domain == "WebKitErrorDomain" && error.code == 102 { -// // Error code 102 "Frame load interrupted" is raised by the WKWebView -// // when the URL is from an http redirect. This is a common pattern when -// // implementing OAuth with a WebView. -// return -// } else { -// delegate?.webView(webView, didReceiveCallbackWithError: error.localizedDescription) -// } -// } -//} From be2a8dd17dcff5bfc656e8e883f036ae84ac5b91 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Tue, 18 May 2021 15:58:44 +0300 Subject: [PATCH 004/110] fixed connection router spec --- Example/Tests/Networking/Routers/ConnectionRouterSpec.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Example/Tests/Networking/Routers/ConnectionRouterSpec.swift b/Example/Tests/Networking/Routers/ConnectionRouterSpec.swift index bb2215e5..1e105f00 100644 --- a/Example/Tests/Networking/Routers/ConnectionRouterSpec.swift +++ b/Example/Tests/Networking/Routers/ConnectionRouterSpec.swift @@ -35,7 +35,7 @@ class ConnectionRouterSpec: BaseSpec { let data = SECreateConnectionRequestData(code: "code", tag: "guid")! let expectedRequest = URLRequestBuilder.buildUrlRequest( - with: baseUrl, + with: baseUrl.appendingPathComponent("/api/authenticator/v1/connections"), method: HTTPMethod.post.rawValue, headers: Headers.requestHeaders(with: "en"), params: RequestParametersBuilder.parameters( From ad2e7b3d149a307bfaf1d135749dd0eed46af84d Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Wed, 19 May 2021 15:44:18 +0300 Subject: [PATCH 005/110] Added detached rsa builder --- .../Authenticator.xcodeproj/project.pbxproj | 30 +- .../SEEncryptedDataExtensions.swift | 1 + .../AuthorizationsInteractor.swift | 1 + .../Interactors/CollectionsInteractor.swift | 1 + .../Interactors/ConnectionsInteractor.swift | 1 + .../Interactors/ConsentsInteractor.swift | 1 + .../Connections/ConnectionsViewModel.swift | 1 + Example/Podfile.lock | 10 +- Example/Tests/Errors/CryptoErrorsSpec.swift | 1 + .../SEEncryptedDataExtensionsSpec.swift | 1 + .../Structures/ConnectionDataSpec.swift | 1 + .../Routers/{ => v1}/ActionRouterSpec.swift | 1 + .../{ => v1}/AuthorizationRouterSpec.swift | 1 + .../{ => v1}/ConnectionRouterSpec.swift | 1 + .../Routers/{ => v1}/ProviderRouterSpec.swift | 1 + .../Tests/Spec Helpers/SpecCryptoHelper.swift | 1 + Example/Tests/Spec Helpers/SpecUtils.swift | 1 + .../Spec Helpers/URLRequestBuilder.swift | 1 + .../Classes/API/Models/BaseStructures.swift | 62 +++ .../Classes/Crypto/SECryptoHelper.swift | 8 +- .../Classes/Crypto/SECryptoHelperError.swift | 6 +- .../Classes}/DateExtensions.swift | 4 +- .../Helpers/ParametersSerializer.swift | 4 +- .../Classes/Routers/Routable.swift | 4 +- .../Classes/API/BaseNetworking.swift | 1 + .../Classes/API/HTTPService.swift | 1 + .../API/Managers/SEAuthorizationManager.swift | 1 + .../API/Managers/SEConnectionManager.swift | 1 + .../API/Managers/SEConsentsManager.swift | 1 + .../API/Models/Requests/BaseStructures.swift | 80 ++-- .../Models/Requests/SEActionRequestData.swift | 1 + .../SEConfirmAuthorizationRequestData.swift | 1 + .../SECreateConnectionRequestData.swift | 1 + .../Responses/AuthorizationResponses.swift | 1 + .../Classes/API/Models/SEEncryptedData.swift | 78 ++-- .../Classes/API/Networking.swift | 1 + .../Classes/API/Routers/Routable.swift | 103 ++--- .../API/Routers/SEProviderRouter.swift | 1 + .../Classes/Crypto/SECryptoHelper.swift | 407 ------------------ .../Classes/Crypto/SECryptoHelperError.swift | 82 ---- .../Classes/Crypto/SETagHelper.swift | 29 -- .../Classes/Helpers/SignatureHelper.swift | 1 + .../Classes/API/Headers.swift | 12 +- .../Classes/RequestParametersBuilder.swift | 1 + .../Classes/Routers/SEConnectionRouter.swift | 17 +- .../Classes/SignatureHelper.swift | 34 +- .../SaltedgeAuthenticatorSDKv2.podspec | 3 +- 47 files changed, 308 insertions(+), 694 deletions(-) rename Example/Tests/Networking/Routers/{ => v1}/ActionRouterSpec.swift (99%) rename Example/Tests/Networking/Routers/{ => v1}/AuthorizationRouterSpec.swift (99%) rename Example/Tests/Networking/Routers/{ => v1}/ConnectionRouterSpec.swift (99%) rename Example/Tests/Networking/Routers/{ => v1}/ProviderRouterSpec.swift (98%) create mode 100644 SaltedgeAuthenticatorCore/Classes/API/Models/BaseStructures.swift rename {SaltedgeAuthenticatorSDK/Classes/Extensions => SaltedgeAuthenticatorCore/Classes}/DateExtensions.swift (96%) delete mode 100644 SaltedgeAuthenticatorSDK/Classes/Crypto/SECryptoHelper.swift delete mode 100644 SaltedgeAuthenticatorSDK/Classes/Crypto/SECryptoHelperError.swift delete mode 100644 SaltedgeAuthenticatorSDK/Classes/Crypto/SETagHelper.swift rename {SaltedgeAuthenticatorCore => SaltedgeAuthenticatorSDKv2}/Classes/API/Headers.swift (85%) rename SaltedgeAuthenticatorSDK/Classes/API/Helpers/ParametersSerializer.swift => SaltedgeAuthenticatorSDKv2/Classes/SignatureHelper.swift (50%) diff --git a/Example/Authenticator.xcodeproj/project.pbxproj b/Example/Authenticator.xcodeproj/project.pbxproj index 8428fe29..ffa82689 100644 --- a/Example/Authenticator.xcodeproj/project.pbxproj +++ b/Example/Authenticator.xcodeproj/project.pbxproj @@ -680,7 +680,6 @@ 9D66E17022D4C0AF00BD59B6 /* Errors */, 9D6575F6232A9AED00DCC53E /* Extensions */, 9D66E17322D4C0AF00BD59B6 /* Models */, - 9DE3246822D398B300EB162A /* Presenters */, 9D6575F9232A9E7300DCC53E /* Helpers */, 9DE3246B22D398ED00EB162A /* Spec Helpers */, 9DE3243B22D3986B00EB162A /* Supporting Files */, @@ -870,10 +869,7 @@ 9D66E1D322D6146800BD59B6 /* Routers */ = { isa = PBXGroup; children = ( - 9D1DEE8023DB5177003C79AC /* ActionRouterSpec.swift */, - 9D66E1D422D6146800BD59B6 /* ConnectionRouterSpec.swift */, - 9D66E1D522D6146800BD59B6 /* AuthorizationRouterSpec.swift */, - 9D66E1D622D6146800BD59B6 /* ProviderRouterSpec.swift */, + 9D6907132655355600A9CE94 /* v1 */, ); path = Routers; sourceTree = ""; @@ -890,6 +886,17 @@ path = Responses; sourceTree = ""; }; + 9D6907132655355600A9CE94 /* v1 */ = { + isa = PBXGroup; + children = ( + 9D1DEE8023DB5177003C79AC /* ActionRouterSpec.swift */, + 9D66E1D422D6146800BD59B6 /* ConnectionRouterSpec.swift */, + 9D66E1D522D6146800BD59B6 /* AuthorizationRouterSpec.swift */, + 9D66E1D622D6146800BD59B6 /* ProviderRouterSpec.swift */, + ); + path = v1; + sourceTree = ""; + }; 9DBAD79D247E55A70023F944 /* Connect */ = { isa = PBXGroup; children = ( @@ -1325,13 +1332,6 @@ path = Fixtures; sourceTree = ""; }; - 9DE3246822D398B300EB162A /* Presenters */ = { - isa = PBXGroup; - children = ( - ); - path = Presenters; - sourceTree = ""; - }; 9DE3246B22D398ED00EB162A /* Spec Helpers */ = { isa = PBXGroup; children = ( @@ -1597,6 +1597,7 @@ "${BUILT_PRODUCTS_DIR}/FirebaseInstallations/FirebaseInstallations.framework", "${BUILT_PRODUCTS_DIR}/GoogleDataTransport/GoogleDataTransport.framework", "${BUILT_PRODUCTS_DIR}/GoogleUtilities/GoogleUtilities.framework", + "${BUILT_PRODUCTS_DIR}/JOSESwift/JOSESwift.framework", "${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework", "${BUILT_PRODUCTS_DIR}/ReachabilitySwift/Reachability.framework", "${BUILT_PRODUCTS_DIR}/Realm/Realm.framework", @@ -1618,6 +1619,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseInstallations.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleDataTransport.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleUtilities.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/JOSESwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBLPromises.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reachability.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework", @@ -1708,6 +1710,7 @@ "${BUILT_PRODUCTS_DIR}/FirebaseInstallations/FirebaseInstallations.framework", "${BUILT_PRODUCTS_DIR}/GoogleDataTransport/GoogleDataTransport.framework", "${BUILT_PRODUCTS_DIR}/GoogleUtilities/GoogleUtilities.framework", + "${BUILT_PRODUCTS_DIR}/JOSESwift/JOSESwift.framework", "${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework", "${BUILT_PRODUCTS_DIR}/ReachabilitySwift/Reachability.framework", "${BUILT_PRODUCTS_DIR}/Realm/Realm.framework", @@ -1731,6 +1734,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseInstallations.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleDataTransport.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleUtilities.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/JOSESwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBLPromises.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reachability.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework", @@ -2294,6 +2298,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 5A77B827BD85D2BAD4D1123E /* Pods-Authenticator_Tests.debug.xcconfig */; buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; DEVELOPMENT_TEAM = 8QFQG87EDC; FRAMEWORK_SEARCH_PATHS = ( "$(PLATFORM_DIR)/Developer/Library/Frameworks", @@ -2318,6 +2323,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = B108DBE02AD4CBDC3AD30A22 /* Pods-Authenticator_Tests.release.xcconfig */; buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; DEVELOPMENT_TEAM = 8QFQG87EDC; FRAMEWORK_SEARCH_PATHS = ( "$(PLATFORM_DIR)/Developer/Library/Frameworks", diff --git a/Example/Authenticator/Utils/Extensions/SEEncryptedDataExtensions.swift b/Example/Authenticator/Utils/Extensions/SEEncryptedDataExtensions.swift index a82c7c79..8c1fbd3a 100644 --- a/Example/Authenticator/Utils/Extensions/SEEncryptedDataExtensions.swift +++ b/Example/Authenticator/Utils/Extensions/SEEncryptedDataExtensions.swift @@ -22,6 +22,7 @@ import Foundation import SEAuthenticator +import SEAuthenticatorCore extension SEEncryptedData { var decryptedAuthorizationData: SEAuthorizationData? { diff --git a/Example/Authenticator/Utils/Interactors/AuthorizationsInteractor.swift b/Example/Authenticator/Utils/Interactors/AuthorizationsInteractor.swift index 32d6590d..fe0451d5 100644 --- a/Example/Authenticator/Utils/Interactors/AuthorizationsInteractor.swift +++ b/Example/Authenticator/Utils/Interactors/AuthorizationsInteractor.swift @@ -22,6 +22,7 @@ import Foundation import SEAuthenticator +import SEAuthenticatorCore struct AuthorizationsInteractor { static func confirm( diff --git a/Example/Authenticator/Utils/Interactors/CollectionsInteractor.swift b/Example/Authenticator/Utils/Interactors/CollectionsInteractor.swift index bff71c4e..9fb10797 100644 --- a/Example/Authenticator/Utils/Interactors/CollectionsInteractor.swift +++ b/Example/Authenticator/Utils/Interactors/CollectionsInteractor.swift @@ -22,6 +22,7 @@ import Foundation import SEAuthenticator +import SEAuthenticatorCore enum CollectionsInteractor { case authorizations diff --git a/Example/Authenticator/Utils/Interactors/ConnectionsInteractor.swift b/Example/Authenticator/Utils/Interactors/ConnectionsInteractor.swift index 71bcd404..28da8153 100644 --- a/Example/Authenticator/Utils/Interactors/ConnectionsInteractor.swift +++ b/Example/Authenticator/Utils/Interactors/ConnectionsInteractor.swift @@ -22,6 +22,7 @@ import Foundation import SEAuthenticator +import SEAuthenticatorCore struct ConnectionsInteractor { static func createNewConnection( diff --git a/Example/Authenticator/Utils/Interactors/ConsentsInteractor.swift b/Example/Authenticator/Utils/Interactors/ConsentsInteractor.swift index a49cbe11..2c24d9fc 100644 --- a/Example/Authenticator/Utils/Interactors/ConsentsInteractor.swift +++ b/Example/Authenticator/Utils/Interactors/ConsentsInteractor.swift @@ -22,6 +22,7 @@ import Foundation import SEAuthenticator +import SEAuthenticatorCore struct ConsentsInteractor { static func revoke(_ consent: SEConsentData, success: (() -> ())? = nil, failure: ((String) -> ())? = nil) { diff --git a/Example/Authenticator/View Models/Connections/ConnectionsViewModel.swift b/Example/Authenticator/View Models/Connections/ConnectionsViewModel.swift index 3dea9154..21ab1e52 100644 --- a/Example/Authenticator/View Models/Connections/ConnectionsViewModel.swift +++ b/Example/Authenticator/View Models/Connections/ConnectionsViewModel.swift @@ -23,6 +23,7 @@ import Foundation import RealmSwift import SEAuthenticator +import SEAuthenticatorCore protocol ConnectionsEventsDelegate: class { func showEditConnectionAlert(placeholder: String, completion: @escaping (String) -> ()) diff --git a/Example/Podfile.lock b/Example/Podfile.lock index ac47162b..56837d4b 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -86,6 +86,7 @@ PODS: - GoogleUtilities/Logger - GoogleUtilities/UserDefaults (7.4.0): - GoogleUtilities/Logger + - JOSESwift (2.4.0) - nanopb (2.30908.0): - nanopb/decode (= 2.30908.0) - nanopb/encode (= 2.30908.0) @@ -106,7 +107,7 @@ PODS: - CryptoSwift - SaltedgeAuthenticatorCore - SaltedgeAuthenticatorSDKv2 (1.1.0): - - CryptoSwift + - JOSESwift - SaltedgeAuthenticatorCore - SDWebImage (5.10.4): - SDWebImage/Core (= 5.10.4) @@ -117,6 +118,7 @@ PODS: DEPENDENCIES: - Firebase/Analytics - Firebase/Crashlytics + - JOSESwift - Nimble - Quick - ReachabilitySwift @@ -140,6 +142,7 @@ SPEC REPOS: - GoogleAppMeasurement - GoogleDataTransport - GoogleUtilities + - JOSESwift - nanopb - Nimble - PromisesObjC @@ -170,6 +173,7 @@ SPEC CHECKSUMS: GoogleAppMeasurement: fd19169c3034975cb934e865e5667bfdce59df7f GoogleDataTransport: cd9db2180fcecd8da1b561aea31e3e56cf834aa7 GoogleUtilities: 284cddc7fffc14ae1907efb6f78ab95c1fccaedc + JOSESwift: 7ff178bb9173ff42c6e990929a9f2fa702a34f69 nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96 Nimble: 4f4a345c80b503b3ea13606a4f98405974ee4d0b PromisesObjC: 3113f7f76903778cf4a0586bd1ab89329a0b7b97 @@ -179,11 +183,11 @@ SPEC CHECKSUMS: RealmSwift: 5a35c1c35c3e1925e6fcd092c881d5cc4a826bff SaltedgeAuthenticatorCore: bf9c91b81449fb7ccd23581ab84e1eeff5020647 SaltedgeAuthenticatorSDK: 4fd566984678e8d936cae541aee82c89b44422e2 - SaltedgeAuthenticatorSDKv2: 2c3536ff2426d12c2624bf9524502f61a70ab458 + SaltedgeAuthenticatorSDKv2: f9eb801575c0b90879c3f7cf667a7aabed743eef SDWebImage: c666b97e1fa9c64b4909816a903322018f0a9c84 TinyConstraints: 8dd91295e56797648c7bc335dd20e1d91ec4c192 Valet: 16d0537d70db79d9ba953b9060b5da4fb8004e51 -PODFILE CHECKSUM: 7f15a08d33a1af46d3f787184ec65b0ed568eed4 +PODFILE CHECKSUM: bbadedc6649010c795a5f299bc2dab1a3b2654ef COCOAPODS: 1.10.1 diff --git a/Example/Tests/Errors/CryptoErrorsSpec.swift b/Example/Tests/Errors/CryptoErrorsSpec.swift index d2523977..48323a9d 100644 --- a/Example/Tests/Errors/CryptoErrorsSpec.swift +++ b/Example/Tests/Errors/CryptoErrorsSpec.swift @@ -22,6 +22,7 @@ import Quick import Nimble +@testable import SEAuthenticatorCore @testable import SEAuthenticator class CryptoErrorsSpec: BaseSpec { diff --git a/Example/Tests/Extensions/SEEncryptedDataExtensionsSpec.swift b/Example/Tests/Extensions/SEEncryptedDataExtensionsSpec.swift index aadf7859..0df026a1 100644 --- a/Example/Tests/Extensions/SEEncryptedDataExtensionsSpec.swift +++ b/Example/Tests/Extensions/SEEncryptedDataExtensionsSpec.swift @@ -22,6 +22,7 @@ import Quick import Nimble +import SEAuthenticatorCore @testable import SEAuthenticator class SEEncryptedDataExtensionsSpec: BaseSpec { diff --git a/Example/Tests/Models/Structures/ConnectionDataSpec.swift b/Example/Tests/Models/Structures/ConnectionDataSpec.swift index 58d94c5f..0160dffa 100644 --- a/Example/Tests/Models/Structures/ConnectionDataSpec.swift +++ b/Example/Tests/Models/Structures/ConnectionDataSpec.swift @@ -22,6 +22,7 @@ import Quick import Nimble +import SEAuthenticatorCore @testable import SEAuthenticator class ConnectionDataSpec: BaseSpec { diff --git a/Example/Tests/Networking/Routers/ActionRouterSpec.swift b/Example/Tests/Networking/Routers/v1/ActionRouterSpec.swift similarity index 99% rename from Example/Tests/Networking/Routers/ActionRouterSpec.swift rename to Example/Tests/Networking/Routers/v1/ActionRouterSpec.swift index 6ac34ad2..3c08e416 100644 --- a/Example/Tests/Networking/Routers/ActionRouterSpec.swift +++ b/Example/Tests/Networking/Routers/v1/ActionRouterSpec.swift @@ -22,6 +22,7 @@ import Quick import Nimble +import SEAuthenticatorCore @testable import SEAuthenticator class ActionRouterSpec: BaseSpec { diff --git a/Example/Tests/Networking/Routers/AuthorizationRouterSpec.swift b/Example/Tests/Networking/Routers/v1/AuthorizationRouterSpec.swift similarity index 99% rename from Example/Tests/Networking/Routers/AuthorizationRouterSpec.swift rename to Example/Tests/Networking/Routers/v1/AuthorizationRouterSpec.swift index 18cd856a..a440f70a 100644 --- a/Example/Tests/Networking/Routers/AuthorizationRouterSpec.swift +++ b/Example/Tests/Networking/Routers/v1/AuthorizationRouterSpec.swift @@ -22,6 +22,7 @@ import Quick import Nimble +import SEAuthenticatorCore @testable import SEAuthenticator class AuthorizationRouterSpec: BaseSpec { diff --git a/Example/Tests/Networking/Routers/ConnectionRouterSpec.swift b/Example/Tests/Networking/Routers/v1/ConnectionRouterSpec.swift similarity index 99% rename from Example/Tests/Networking/Routers/ConnectionRouterSpec.swift rename to Example/Tests/Networking/Routers/v1/ConnectionRouterSpec.swift index 1e105f00..77ee20f3 100644 --- a/Example/Tests/Networking/Routers/ConnectionRouterSpec.swift +++ b/Example/Tests/Networking/Routers/v1/ConnectionRouterSpec.swift @@ -22,6 +22,7 @@ import Quick import Nimble +import SEAuthenticatorCore @testable import SEAuthenticator class ConnectionRouterSpec: BaseSpec { diff --git a/Example/Tests/Networking/Routers/ProviderRouterSpec.swift b/Example/Tests/Networking/Routers/v1/ProviderRouterSpec.swift similarity index 98% rename from Example/Tests/Networking/Routers/ProviderRouterSpec.swift rename to Example/Tests/Networking/Routers/v1/ProviderRouterSpec.swift index 621b8736..732c5175 100644 --- a/Example/Tests/Networking/Routers/ProviderRouterSpec.swift +++ b/Example/Tests/Networking/Routers/v1/ProviderRouterSpec.swift @@ -22,6 +22,7 @@ import Quick import Nimble +import SEAuthenticatorCore @testable import SEAuthenticator class ProviderRouterSpec: BaseSpec { diff --git a/Example/Tests/Spec Helpers/SpecCryptoHelper.swift b/Example/Tests/Spec Helpers/SpecCryptoHelper.swift index 69663e2b..8cd16d6b 100644 --- a/Example/Tests/Spec Helpers/SpecCryptoHelper.swift +++ b/Example/Tests/Spec Helpers/SpecCryptoHelper.swift @@ -21,6 +21,7 @@ // import Foundation +import SEAuthenticatorCore @testable import SEAuthenticator struct SpecCryptoHelper { diff --git a/Example/Tests/Spec Helpers/SpecUtils.swift b/Example/Tests/Spec Helpers/SpecUtils.swift index 3d6cc8cd..b203f047 100644 --- a/Example/Tests/Spec Helpers/SpecUtils.swift +++ b/Example/Tests/Spec Helpers/SpecUtils.swift @@ -22,6 +22,7 @@ import Foundation import SEAuthenticator +import SEAuthenticatorCore struct SpecUtils { static func createConnection(id: ID) -> Connection { diff --git a/Example/Tests/Spec Helpers/URLRequestBuilder.swift b/Example/Tests/Spec Helpers/URLRequestBuilder.swift index aab144fa..1bafce52 100644 --- a/Example/Tests/Spec Helpers/URLRequestBuilder.swift +++ b/Example/Tests/Spec Helpers/URLRequestBuilder.swift @@ -21,6 +21,7 @@ // import Foundation +import SEAuthenticatorCore @testable import SEAuthenticator struct URLRequestBuilder { diff --git a/SaltedgeAuthenticatorCore/Classes/API/Models/BaseStructures.swift b/SaltedgeAuthenticatorCore/Classes/API/Models/BaseStructures.swift new file mode 100644 index 00000000..ab6c9743 --- /dev/null +++ b/SaltedgeAuthenticatorCore/Classes/API/Models/BaseStructures.swift @@ -0,0 +1,62 @@ +// +// BaseStructures +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation + +open class SEBaseAuthenticatedRequestData { + public let url: URL + public let connectionGuid: GUID + public let accessToken: AccessToken + public let appLanguage: ApplicationLanguage + + public init( + url: URL, + connectionGuid: GUID, + accessToken: AccessToken, + appLanguage: ApplicationLanguage + ) { + self.url = url + self.connectionGuid = connectionGuid + self.accessToken = accessToken + self.appLanguage = appLanguage + } +} + +open class SEBaseAuthenticatedWithIdRequestData: SEBaseAuthenticatedRequestData { + public let entityId: ID + + public init( + url: URL, + connectionGuid: GUID, + accessToken: AccessToken, + appLanguage: ApplicationLanguage, + entityId: ID + ) { + self.entityId = entityId + super.init( + url: url, + connectionGuid: connectionGuid, + accessToken: accessToken, + appLanguage: appLanguage + ) + } +} diff --git a/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelper.swift b/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelper.swift index d18033c0..e4725fd2 100644 --- a/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelper.swift +++ b/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelper.swift @@ -80,6 +80,10 @@ public struct SECryptoHelper { return try SecKeyHelper.obtainKeyData(for: tag) } + public static func publicKey(for tag: KeyTag) throws -> SecKey { + return try SecKeyHelper.obtainKey(for: tag) + } + public static func privateKey(for tag: KeyTag) throws -> SecKey { return try SecKeyHelper.obtainKey(for: tag.privateTag) } @@ -334,7 +338,7 @@ private struct SecKeyHelper { } // MARK: - Public Extensions -extension Data { +public extension Data { var string: String { let beginPublicKey = "-----BEGIN PUBLIC KEY-----\n" let endPublicKey = "\n-----END PUBLIC KEY-----\n" @@ -346,7 +350,7 @@ extension Data { } // MARK: - Private Extensions -extension Data { +public extension Data { func dataByPrependingX509Header() -> Data { let result = NSMutableData() diff --git a/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelperError.swift b/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelperError.swift index 7f63852d..724e4b9e 100644 --- a/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelperError.swift +++ b/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelperError.swift @@ -23,7 +23,7 @@ import Foundation -enum SECryptoHelperError: Error { +public enum SECryptoHelperError: Error { case errorGeneratingRandomBytes case errorCreatingData(fromBase64: String) case couldNotEncryptChunk(at: Int) @@ -45,7 +45,7 @@ enum SECryptoHelperError: Error { } } -enum SEAesCipherError: Error { +public enum SEAesCipherError: Error { case couldNotCreateData(from: String) case couldNotCreateString(fromBase64: String) case couldNotCreateEncryptedData(fromBase64: String) @@ -70,7 +70,7 @@ enum SEAesCipherError: Error { } } -enum SESecKeyHelperError: Error { +public enum SESecKeyHelperError: Error { case couldNotObtainKey(for: String) case couldNotObtainKeyData(for: String) case couldNotAddToKeychain diff --git a/SaltedgeAuthenticatorSDK/Classes/Extensions/DateExtensions.swift b/SaltedgeAuthenticatorCore/Classes/DateExtensions.swift similarity index 96% rename from SaltedgeAuthenticatorSDK/Classes/Extensions/DateExtensions.swift rename to SaltedgeAuthenticatorCore/Classes/DateExtensions.swift index 05cd64e4..451515cd 100644 --- a/SaltedgeAuthenticatorSDK/Classes/Extensions/DateExtensions.swift +++ b/SaltedgeAuthenticatorCore/Classes/DateExtensions.swift @@ -1,8 +1,8 @@ // -// DateExtensions.swift +// DateExtensions // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by diff --git a/SaltedgeAuthenticatorCore/Classes/Helpers/ParametersSerializer.swift b/SaltedgeAuthenticatorCore/Classes/Helpers/ParametersSerializer.swift index a4721301..a5cdbf6c 100644 --- a/SaltedgeAuthenticatorCore/Classes/Helpers/ParametersSerializer.swift +++ b/SaltedgeAuthenticatorCore/Classes/Helpers/ParametersSerializer.swift @@ -22,8 +22,8 @@ import Foundation -struct ParametersSerializer { - static func createBody(parameters: [String: Any]) -> Data? { +public struct ParametersSerializer { + public static func createBody(parameters: [String: Any]) -> Data? { do { var data: Data if #available(iOS 11.0, *) { diff --git a/SaltedgeAuthenticatorCore/Classes/Routers/Routable.swift b/SaltedgeAuthenticatorCore/Classes/Routers/Routable.swift index 09782120..ae130924 100644 --- a/SaltedgeAuthenticatorCore/Classes/Routers/Routable.swift +++ b/SaltedgeAuthenticatorCore/Classes/Routers/Routable.swift @@ -56,8 +56,8 @@ public extension Routable { request.url = components?.url } - if request.value(forHTTPHeaderField: HeadersKeys.contentType) == nil { - request.setValue("application/json", forHTTPHeaderField: HeadersKeys.contentType) + if request.value(forHTTPHeaderField: "Content-Type") == nil { + request.setValue("application/json", forHTTPHeaderField: "Content-Type") } request.httpBody = ParametersSerializer.createBody(parameters: parameters) diff --git a/SaltedgeAuthenticatorSDK/Classes/API/BaseNetworking.swift b/SaltedgeAuthenticatorSDK/Classes/API/BaseNetworking.swift index 2a155d84..1aba0c41 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/BaseNetworking.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/BaseNetworking.swift @@ -21,6 +21,7 @@ // import Foundation +import SEAuthenticatorCore protocol Networking { static func execute(_ urlRequest: Routable, success: @escaping RequestSuccessBlock, failure: @escaping FailureBlock) diff --git a/SaltedgeAuthenticatorSDK/Classes/API/HTTPService.swift b/SaltedgeAuthenticatorSDK/Classes/API/HTTPService.swift index 046a1250..1844a110 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/HTTPService.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/HTTPService.swift @@ -21,6 +21,7 @@ // import Foundation +import SEAuthenticatorCore struct HTTPService { static func execute(request: Routable, diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEAuthorizationManager.swift b/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEAuthorizationManager.swift index 584e37b8..4c582ca6 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEAuthorizationManager.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEAuthorizationManager.swift @@ -21,6 +21,7 @@ // import Foundation +import SEAuthenticatorCore public struct SEAuthorizationManager { public static func getEncryptedAuthorizations( diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEConnectionManager.swift b/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEConnectionManager.swift index 18df2ba5..2f0a9ac0 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEConnectionManager.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEConnectionManager.swift @@ -21,6 +21,7 @@ // import Foundation +import SEAuthenticatorCore public struct SEConnectionManager { public static func createConnection( diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEConsentsManager.swift b/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEConsentsManager.swift index 81668194..baf5c7a1 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEConsentsManager.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEConsentsManager.swift @@ -21,6 +21,7 @@ // import Foundation +import SEAuthenticatorCore public struct SEConsentsManager { public static func getEncryptedConsents( diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/BaseStructures.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/BaseStructures.swift index 30851bed..829c13e4 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/BaseStructures.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/BaseStructures.swift @@ -20,43 +20,43 @@ // under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md // -import Foundation - -public class SEBaseAuthenticatedRequestData { - public let url: URL - public let connectionGuid: GUID - public let accessToken: AccessToken - public let appLanguage: ApplicationLanguage - - public init( - url: URL, - connectionGuid: GUID, - accessToken: AccessToken, - appLanguage: ApplicationLanguage - ) { - self.url = url - self.connectionGuid = connectionGuid - self.accessToken = accessToken - self.appLanguage = appLanguage - } -} - -public class SEBaseAuthenticatedWithIdRequestData: SEBaseAuthenticatedRequestData { - public let entityId: ID - - public init( - url: URL, - connectionGuid: GUID, - accessToken: AccessToken, - appLanguage: ApplicationLanguage, - entityId: ID - ) { - self.entityId = entityId - super.init( - url: url, - connectionGuid: connectionGuid, - accessToken: accessToken, - appLanguage: appLanguage - ) - } -} +//import Foundation +// +//public class SEBaseAuthenticatedRequestData { +// public let url: URL +// public let connectionGuid: GUID +// public let accessToken: AccessToken +// public let appLanguage: ApplicationLanguage +// +// public init( +// url: URL, +// connectionGuid: GUID, +// accessToken: AccessToken, +// appLanguage: ApplicationLanguage +// ) { +// self.url = url +// self.connectionGuid = connectionGuid +// self.accessToken = accessToken +// self.appLanguage = appLanguage +// } +//} +// +//public class SEBaseAuthenticatedWithIdRequestData: SEBaseAuthenticatedRequestData { +// public let entityId: ID +// +// public init( +// url: URL, +// connectionGuid: GUID, +// accessToken: AccessToken, +// appLanguage: ApplicationLanguage, +// entityId: ID +// ) { +// self.entityId = entityId +// super.init( +// url: url, +// connectionGuid: connectionGuid, +// accessToken: accessToken, +// appLanguage: appLanguage +// ) +// } +//} diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/SEActionRequestData.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/SEActionRequestData.swift index 1f655ee6..4997f449 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/SEActionRequestData.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/SEActionRequestData.swift @@ -21,6 +21,7 @@ // import Foundation +import SEAuthenticatorCore public class SEActionRequestData: SEBaseAuthenticatedRequestData { public let guid: GUID diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/SEConfirmAuthorizationRequestData.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/SEConfirmAuthorizationRequestData.swift index aefea34f..8d4d9441 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/SEConfirmAuthorizationRequestData.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/SEConfirmAuthorizationRequestData.swift @@ -21,6 +21,7 @@ // import Foundation +import SEAuthenticatorCore public class SEConfirmAuthorizationRequestData: SEBaseAuthenticatedWithIdRequestData { public let authorizationCode: String? diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/SECreateConnectionRequestData.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/SECreateConnectionRequestData.swift index ba0e7bc8..2694966f 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/SECreateConnectionRequestData.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/SECreateConnectionRequestData.swift @@ -21,6 +21,7 @@ // import Foundation +import SEAuthenticatorCore public struct SECreateConnectionRequestData { public let providerCode: String diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/AuthorizationResponses.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/AuthorizationResponses.swift index 2b6a189f..aea18cd4 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/AuthorizationResponses.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/AuthorizationResponses.swift @@ -21,6 +21,7 @@ // import Foundation +import SEAuthenticatorCore public struct SEEncryptedDataResponse: SerializableResponse { public var data: SEEncryptedData diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/SEEncryptedData.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/SEEncryptedData.swift index 40ec80c4..75bbb63e 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/SEEncryptedData.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/SEEncryptedData.swift @@ -22,42 +22,42 @@ import Foundation -public struct SEEncryptedData: SerializableResponse, Equatable { - private let defaultAlgorithm = "AES-256-CBC" - - public let data: String - public let key: String - public let iv: String - public var connectionId: String? - - public init(data: String, key: String, iv: String) { - self.data = data - self.key = key - self.iv = iv - } - - public init?(_ value: Any) { - if let dict = value as? [String: Any], - let data = dict[SENetKeys.data] as? String, - let key = dict[SENetKeys.key] as? String, - let iv = dict[SENetKeys.iv] as? String, - let algorithm = dict[SENetKeys.algorithm] as? String, - algorithm == defaultAlgorithm { - self.data = data - self.key = key - self.iv = iv - if let connectionId = dict[SENetKeys.connectionId] as? String { - self.connectionId = connectionId - } - } else { - return nil - } - } - - public static func == (lhs: SEEncryptedData, rhs: SEEncryptedData) -> Bool { - return lhs.data == rhs.data && - lhs.key == rhs.key && - lhs.iv == rhs.iv && - lhs.connectionId == rhs.connectionId - } -} +//public struct SEEncryptedData: SerializableResponse, Equatable { +// private let defaultAlgorithm = "AES-256-CBC" +// +// public let data: String +// public let key: String +// public let iv: String +// public var connectionId: String? +// +// public init(data: String, key: String, iv: String) { +// self.data = data +// self.key = key +// self.iv = iv +// } +// +// public init?(_ value: Any) { +// if let dict = value as? [String: Any], +// let data = dict[SENetKeys.data] as? String, +// let key = dict[SENetKeys.key] as? String, +// let iv = dict[SENetKeys.iv] as? String, +// let algorithm = dict[SENetKeys.algorithm] as? String, +// algorithm == defaultAlgorithm { +// self.data = data +// self.key = key +// self.iv = iv +// if let connectionId = dict[SENetKeys.connectionId] as? String { +// self.connectionId = connectionId +// } +// } else { +// return nil +// } +// } +// +// public static func == (lhs: SEEncryptedData, rhs: SEEncryptedData) -> Bool { +// return lhs.data == rhs.data && +// lhs.key == rhs.key && +// lhs.iv == rhs.iv && +// lhs.connectionId == rhs.connectionId +// } +//} diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Networking.swift b/SaltedgeAuthenticatorSDK/Classes/API/Networking.swift index b4b99916..a890c89f 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Networking.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Networking.swift @@ -21,6 +21,7 @@ // import Foundation +import SEAuthenticatorCore extension Networking { static func execute(_ request: Routable, diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Routers/Routable.swift b/SaltedgeAuthenticatorSDK/Classes/API/Routers/Routable.swift index ff9d8478..a5370726 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Routers/Routable.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Routers/Routable.swift @@ -20,54 +20,55 @@ // under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md // -import Foundation - -enum HTTPMethod: String { - case get = "GET" - case post = "POST" - case put = "PUT" - case delete = "DELETE" -} - -enum Encoding: String { - case url - case json -} - -protocol Routable { - var method: HTTPMethod { get } - var encoding: Encoding { get } - var url: URL { get } - var headers: [String: String]? { get } - var parameters: [String: Any]? { get } -} - -extension Routable { - func asURLRequest() -> URLRequest { - var request = URLRequest(url: url) - request.httpMethod = method.rawValue - request.allHTTPHeaderFields = headers - - guard let parameters = parameters else { return request } - - if encoding == .url { - var components = URLComponents(url: url, resolvingAgainstBaseURL: true) - components?.queryItems = parameters.asUrlQueryItems() - request.url = components?.url - } - - if request.value(forHTTPHeaderField: HeadersKeys.contentType) == nil { - request.setValue("application/json", forHTTPHeaderField: HeadersKeys.contentType) - } - - request.httpBody = ParametersSerializer.createBody(parameters: parameters) - - return request - } -} - -private extension Dictionary { - func asUrlQueryItems() -> [URLQueryItem] { - return map { URLQueryItem(name: "\($0.0)", value: "\($0.1)") } - } -} +//import Foundation +//import SEAuthenticatorCore +// +//enum HTTPMethod: String { +// case get = "GET" +// case post = "POST" +// case put = "PUT" +// case delete = "DELETE" +//} +// +//enum Encoding: String { +// case url +// case json +//} +// +//protocol Routable { +// var method: HTTPMethod { get } +// var encoding: Encoding { get } +// var url: URL { get } +// var headers: [String: String]? { get } +// var parameters: [String: Any]? { get } +//} +// +//extension Routable { +// func asURLRequest() -> URLRequest { +// var request = URLRequest(url: url) +// request.httpMethod = method.rawValue +// request.allHTTPHeaderFields = headers +// +// guard let parameters = parameters else { return request } +// +// if encoding == .url { +// var components = URLComponents(url: url, resolvingAgainstBaseURL: true) +// components?.queryItems = parameters.asUrlQueryItems() +// request.url = components?.url +// } +// +// if request.value(forHTTPHeaderField: HeadersKeys.contentType) == nil { +// request.setValue("application/json", forHTTPHeaderField: HeadersKeys.contentType) +// } +// +// request.httpBody = ParametersSerializer.createBody(parameters: parameters) +// +// return request +// } +//} +// +//private extension Dictionary { +// func asUrlQueryItems() -> [URLQueryItem] { +// return map { URLQueryItem(name: "\($0.0)", value: "\($0.1)") } +// } +//} diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEProviderRouter.swift b/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEProviderRouter.swift index 051014aa..5cdf8fb3 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEProviderRouter.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEProviderRouter.swift @@ -21,6 +21,7 @@ // import Foundation +import SEAuthenticatorCore enum SEProviderRouter: Routable { case fetchData(URL) diff --git a/SaltedgeAuthenticatorSDK/Classes/Crypto/SECryptoHelper.swift b/SaltedgeAuthenticatorSDK/Classes/Crypto/SECryptoHelper.swift deleted file mode 100644 index 95d11da5..00000000 --- a/SaltedgeAuthenticatorSDK/Classes/Crypto/SECryptoHelper.swift +++ /dev/null @@ -1,407 +0,0 @@ -// -// SECryptoHelper.swift -// This file is part of the Salt Edge Authenticator distribution -// (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 3 or later. -// -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -// For the additional permissions granted for Salt Edge Authenticator -// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md -// - -import Foundation -import CryptoSwift - -public typealias KeyTag = String -public typealias KeyPair = (publicKey: SecKey, privateKey: SecKey) - -public struct SECryptoHelper { - // MARK: - Public Methods - public static func createKeyPair(with tag: KeyTag) -> KeyPair? { - SecKeyHelper.deleteKey(tag) - SecKeyHelper.deleteKey(tag.privateTag) - - return SecKeyHelper.generateKeyPair(tag: tag) - } - - public static func createKey(fromFile name: String, isPublic: Bool, tag: String) throws -> SecKey { - SecKeyHelper.deleteKey(tag) - SecKeyHelper.deleteKey(tag.privateTag) - - return try SecKeyHelper.createKey(fromFile: name, isPublic: isPublic, tag: tag) - } - - @discardableResult - public static func deleteKeyPair(with tag: KeyTag) -> Bool { - SecKeyHelper.deleteKey(tag.privateTag) - - return SecKeyHelper.deleteKey(tag) - } - - public static func encrypt(_ message: String, tag: KeyTag) throws -> SEEncryptedData { - let key = try generateRandomBytes(count: 32) - let iv = try generateRandomBytes(count: 16) - - let encryptedKey = try publicEncrypt(data: key, keyForTag: tag) - let encryptedIv = try publicEncrypt(data: iv, keyForTag: tag) - - return SEEncryptedData( - data: try AesCipher.encrypt(message: message, key: key, iv: iv), - key: encryptedKey.base64EncodedString(), - iv: encryptedIv.base64EncodedString() - ) - } - - public static func decrypt(_ encryptedData: SEEncryptedData, tag: KeyTag) throws -> String { - let privateKey = try SecKeyHelper.obtainKey(for: tag.privateTag) - - return try decrypt(encryptedData, privateKey: privateKey) - } - - public static func decrypt(_ encryptedData: SEEncryptedData, privateKey: SecKey) throws -> String { - let decryptedKey = try privateDecrypt(message: encryptedData.key, privateKey: privateKey) - let decryptedIv = try privateDecrypt(message: encryptedData.iv, privateKey: privateKey) - - return try AesCipher.decrypt(data: encryptedData.data, key: decryptedKey, iv: decryptedIv) - } - - public static func publicKeyData(for tag: KeyTag) throws -> Data { - return try SecKeyHelper.obtainKeyData(for: tag) - } - - public static func privateKey(for tag: KeyTag) throws -> SecKey { - return try SecKeyHelper.obtainKey(for: tag.privateTag) - } - - // MARK: - Private Methods - private static func privateDecrypt(message: String, privateKey: SecKey) throws -> Data { - guard let data = Data(base64Encoded: message.replacingOccurrences(of: "\n", with: "")) else { - throw SECryptoHelperError.errorCreatingData(fromBase64: message) - } - - let blockSize = SecKeyGetBlockSize(privateKey) - - var encryptedDataAsArray = [UInt8](repeating: 0, count: data.count) - (data as NSData).getBytes(&encryptedDataAsArray, length: data.count) - - var decryptedDataBytes = [UInt8](repeating: 0, count: 0) - var idx = 0 - while idx < encryptedDataAsArray.count { - - let idxEnd = min(idx + blockSize, encryptedDataAsArray.count) - let chunkData = [UInt8](encryptedDataAsArray[idx..(decryptedDataBytes), count: decryptedDataBytes.count) - } - - private static func publicEncrypt(data: Data, keyForTag: KeyTag) throws -> Data { - let publicKey = try SecKeyHelper.obtainKey(for: keyForTag) - - let blockSize = SecKeyGetBlockSize(publicKey) - let maxChunkSize = blockSize - 11 // Since PKCS1 padding is used - - var decryptedDataAsArray = [UInt8](repeating: 0, count: data.count) - (data as NSData).getBytes(&decryptedDataAsArray, length: data.count) - - var encryptedDataBytes = [UInt8](repeating: 0, count: 0) - var idx = 0 - while idx < decryptedDataAsArray.count { - - let idxEnd = min(idx + maxChunkSize, decryptedDataAsArray.count) - let chunkData = [UInt8](decryptedDataAsArray[idx..(encryptedDataBytes), count: encryptedDataBytes.count) - } - - private static func generateRandomBytes(count: Int) throws -> Data { - let keyData = Data(count: count) - var newData = keyData - let result = newData.withUnsafeMutableBytes { SecRandomCopyBytes(kSecRandomDefault, keyData.count, $0.baseAddress!) } - if result == errSecSuccess { - return keyData - } else { - throw SECryptoHelperError.errorGeneratingRandomBytes - } - } -} - -private struct AesCipher { - static func encrypt(message: String, key: Data, iv: Data) throws -> String { - guard let data = message.data(using: .utf8) else { - throw SEAesCipherError.couldNotCreateData(from: message) - } - - let keyArray = [UInt8](key) - let ivArray = [UInt8](iv) - - do { - let enc = try AES(key: keyArray, blockMode: CBC(iv: ivArray)).encrypt(data.bytes) - let encData = NSData(bytes: enc, length: Int(enc.count)) - let base64String: String = encData.base64EncodedString(options: []) - - return String(base64String) - } catch { - throw error - } - } - - static func decrypt(data: String, key: Data, iv: Data) throws -> String { - guard let encryptedData = Data(base64Encoded: data, options: [.ignoreUnknownCharacters]) else { - throw SEAesCipherError.couldNotCreateEncryptedData(fromBase64: data) - } - - let keyArray = [UInt8](key) - let ivArray = [UInt8](iv) - - if keyArray.isEmpty { throw SEAesCipherError.noKeyProvided } - if iv.isEmpty { throw SEAesCipherError.noIvProvided } - do { - let dec = try AES(key: keyArray, blockMode: CBC(iv: ivArray)).decrypt(encryptedData.bytes) - let decData = NSData(bytes: dec, length: Int(dec.count)) - - guard let result = String(data: decData as Data, encoding: .utf8) else { - throw SEAesCipherError.couldNotCreateDecodedString(fromData: decData as Data) - } - - return String(result) - } catch { - throw error - } - } -} - -private struct SecKeyHelper { - static func generateKeyPair(tag: KeyTag) -> KeyPair? { - let privateAttributes = [ - String(kSecAttrIsPermanent): true, - String(kSecAttrApplicationTag): tag.privateTag, - String(kSecAttrAccessible): kSecAttrAccessibleWhenUnlockedThisDeviceOnly - ] as [String: Any] - - let publicAttributes = [ - String(kSecAttrIsPermanent): true, - String(kSecAttrApplicationTag): tag, - String(kSecAttrAccessible): kSecAttrAccessibleWhenUnlockedThisDeviceOnly - ] as [String: Any] - - let pairAttributes = [ - String(kSecAttrKeyType): kSecAttrKeyTypeRSA, - String(kSecAttrKeySizeInBits): 2048 as UInt, - String(kSecPublicKeyAttrs): publicAttributes, - String(kSecPrivateKeyAttrs): privateAttributes - ] as [String: Any] - - var publicRef, privateRef: SecKey? - switch SecKeyGeneratePair(pairAttributes as CFDictionary, &publicRef, &privateRef) { - case noErr: - if let publicKey = publicRef, let privateKey = privateRef { - return (publicKey, privateKey) - } - default: break - } - return nil - } - - static func createKey(fromFile name: String, isPublic: Bool, tag: String) throws -> SecKey { - let base64Encoded = base64String(pemEncoded: pem(name)) - - guard let data = Data(base64Encoded: base64Encoded, options: [.ignoreUnknownCharacters]) else { - throw SECryptoHelperError.errorCreatingData(fromBase64: base64Encoded) - } - - let keyClass = isPublic ? kSecAttrKeyClassPublic : kSecAttrKeyClassPrivate - - let persistKey = UnsafeMutablePointer(mutating: nil) - - let keyAddDict: [String: Any] = [ - String(kSecClass): kSecClassKey, - String(kSecAttrApplicationTag): tag, - String(kSecAttrKeyType): kSecAttrKeyTypeRSA, - String(kSecValueData): data as CFData, - String(kSecAttrKeyClass): keyClass, - String(kSecReturnPersistentRef): true as CFBoolean, - String(kSecAttrAccessible): kSecAttrAccessibleWhenUnlockedThisDeviceOnly - ] - - let secStatus = SecItemAdd(keyAddDict as CFDictionary, persistKey) - guard secStatus == errSecSuccess || secStatus == errSecDuplicateItem else { - throw SESecKeyHelperError.couldNotAddToKeychain - } - - return try obtainKey(for: tag) - } - - static func obtainKeyData(for tag: KeyTag) throws -> Data { - var keyRef: AnyObject? - let query: [String: AnyObject] = [ - String(kSecAttrKeyType): kSecAttrKeyTypeRSA, - String(kSecReturnData): kCFBooleanTrue as CFBoolean, - String(kSecClass): kSecClassKey as CFString, - String(kSecAttrApplicationTag): tag as CFString - ] - - switch SecItemCopyMatching(query as CFDictionary, &keyRef) { - case noErr: - guard let ref = keyRef as? Data else { throw SESecKeyHelperError.couldNotObtainKeyData(for: tag) } - - return ref - default: - throw SESecKeyHelperError.couldNotObtainKeyData(for: tag) - } - } - - static func obtainKey(for tag: KeyTag) throws -> SecKey { - var keyRef: AnyObject? - let query: [String: AnyObject] = [ - String(kSecAttrKeyType): kSecAttrKeyTypeRSA, - String(kSecReturnRef): kCFBooleanTrue as CFBoolean, - String(kSecClass): kSecClassKey as CFString, - String(kSecAttrApplicationTag): tag as CFString - ] - - switch SecItemCopyMatching(query as CFDictionary, &keyRef) { - case noErr: - guard let ref = keyRef else { throw SESecKeyHelperError.couldNotObtainKey(for: tag) } - - // swiftlint:disable:next force_cast - return (ref as! SecKey) - default: - throw SESecKeyHelperError.couldNotObtainKey(for: tag) - } - } - - @discardableResult - static func deleteKey(_ tag: KeyTag) -> Bool { - let query: [String: AnyObject] = [ - String(kSecAttrKeyType): kSecAttrKeyTypeRSA, - String(kSecClass): kSecClassKey as CFString, - String(kSecAttrApplicationTag): tag as CFString - ] - - return SecItemDelete(query as CFDictionary) == noErr - } - - static func base64String(pemEncoded pemString: String) -> String { - return pemString.components(separatedBy: "\n").filter { line in - return !line.hasPrefix("-----BEGIN") && !line.hasPrefix("-----END") - }.joined(separator: "") - } - - static func pem(_ filename: String) -> String { - let path = Bundle.main.path(forResource: filename, ofType: "pem")! - // swiftlint:disable:next force_try - let pemString = try! String(contentsOfFile: path, encoding: .utf8) - return pemString - } -} - -// MARK: - Public Extensions -extension Data { - var string: String { - let beginPublicKey = "-----BEGIN PUBLIC KEY-----\n" - let endPublicKey = "\n-----END PUBLIC KEY-----\n" - let base64PublicKey = dataByPrependingX509Header() - .base64EncodedString(options: [.lineLength64Characters, .endLineWithLineFeed]) - - return (beginPublicKey + base64PublicKey + endPublicKey) - } -} - -// MARK: - Private Extensions -extension Data { - func dataByPrependingX509Header() -> Data { - let result = NSMutableData() - - let encodingLength: Int = (self.count + 1).encodedOctets().count - let OID: [CUnsignedChar] = [0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, - 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00] - - var builder: [CUnsignedChar] = [] - - // ASN.1 SEQUENCE - builder.append(0x30) - - // Overall size, made of OID + bitstring encoding + actual key - let size = OID.count + 2 + encodingLength + self.count - let encodedSize = size.encodedOctets() - builder.append(contentsOf: encodedSize) - result.append(builder, length: builder.count) - result.append(OID, length: OID.count) - builder.removeAll(keepingCapacity: false) - - builder.append(0x03) - builder.append(contentsOf: (self.count + 1).encodedOctets()) - builder.append(0x00) - result.append(builder, length: builder.count) - - // Actual key bytes - result.append(self) - - return result as Data - } -} - -extension NSInteger { - func encodedOctets() -> [CUnsignedChar] { - // Short form - if self < 128 { - return [CUnsignedChar(self)] - } - - // Long form - let i = Int(log2(Double(self)) / 8 + 1) - var len = self - var result: [CUnsignedChar] = [CUnsignedChar(i + 0x80)] - - for _ in 0..> 8 - } - - return result - } -} - -private extension KeyTag { - var privateTag: KeyTag { - return self + ".private" - } -} diff --git a/SaltedgeAuthenticatorSDK/Classes/Crypto/SECryptoHelperError.swift b/SaltedgeAuthenticatorSDK/Classes/Crypto/SECryptoHelperError.swift deleted file mode 100644 index 7f63852d..00000000 --- a/SaltedgeAuthenticatorSDK/Classes/Crypto/SECryptoHelperError.swift +++ /dev/null @@ -1,82 +0,0 @@ -// -// SECryptoHelperError.swift -// SaltedgeAuthenticatorSDK -// This file is part of the Salt Edge Authenticator distribution -// (https://github.com/saltedge/sca-authenticator-ios) -// Copyright (c) 2019 Salt Edge Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 3 or later. -// -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -// For the additional permissions granted for Salt Edge Authenticator -// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md - - -import Foundation - -enum SECryptoHelperError: Error { - case errorGeneratingRandomBytes - case errorCreatingData(fromBase64: String) - case couldNotEncryptChunk(at: Int) - case couldNotDecryptChunk(at: Int) - - var localizedDescription: String { - var message = "" - switch self { - case .errorCreatingData(let string): - message = "from base 64 encoded \(string)" - case .couldNotEncryptChunk(let index): - message = "at index: \(index)" - case .couldNotDecryptChunk(let index): - message = "at index: \(index)" - default: - break - } - return "\(String(describing: type(of: self))).\(String(describing: self)) \(message)" - } -} - -enum SEAesCipherError: Error { - case couldNotCreateData(from: String) - case couldNotCreateString(fromBase64: String) - case couldNotCreateEncryptedData(fromBase64: String) - case couldNotCreateDecodedString(fromData: Data) - case noKeyProvided - case noIvProvided - - var localizedDescription: String { - var message = "" - switch self { - case .couldNotCreateData(let string), - .couldNotCreateString(let string), - .couldNotCreateEncryptedData(let string): - message = string - case .couldNotCreateDecodedString(let data): - message = data.base64EncodedString() - default: - break - } - - return "\(String(describing: type(of: self))).\(String(describing: self)) \(message)" - } -} - -enum SESecKeyHelperError: Error { - case couldNotObtainKey(for: String) - case couldNotObtainKeyData(for: String) - case couldNotAddToKeychain - - var localizedDescription: String { - return "\(String(describing: type(of: self))).\(String(describing: self))" - } -} - diff --git a/SaltedgeAuthenticatorSDK/Classes/Crypto/SETagHelper.swift b/SaltedgeAuthenticatorSDK/Classes/Crypto/SETagHelper.swift deleted file mode 100644 index 4e422164..00000000 --- a/SaltedgeAuthenticatorSDK/Classes/Crypto/SETagHelper.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// SETagHelper.swift -// This file is part of the Salt Edge Authenticator distribution -// (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 3 or later. -// -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -// For the additional permissions granted for Salt Edge Authenticator -// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md -// - -import Foundation - -public struct SETagHelper { - public static func create(for code: String) -> String { - return "com.saltedge.authenticator.\(code)" - } -} diff --git a/SaltedgeAuthenticatorSDK/Classes/Helpers/SignatureHelper.swift b/SaltedgeAuthenticatorSDK/Classes/Helpers/SignatureHelper.swift index 53069271..0cef0360 100644 --- a/SaltedgeAuthenticatorSDK/Classes/Helpers/SignatureHelper.swift +++ b/SaltedgeAuthenticatorSDK/Classes/Helpers/SignatureHelper.swift @@ -22,6 +22,7 @@ import Foundation import CommonCrypto +import SEAuthenticatorCore struct SignatureHelper { static func signedPayload(method: HTTPMethod, diff --git a/SaltedgeAuthenticatorCore/Classes/API/Headers.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Headers.swift similarity index 85% rename from SaltedgeAuthenticatorCore/Classes/API/Headers.swift rename to SaltedgeAuthenticatorSDKv2/Classes/API/Headers.swift index d998d4e8..2b0f60dd 100644 --- a/SaltedgeAuthenticatorCore/Classes/API/Headers.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Headers.swift @@ -1,5 +1,5 @@ // -// Headers.swift +// Headers // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) // Copyright © 2021 Salt Edge Inc. @@ -31,16 +31,16 @@ struct HeadersKeys { static let signature = "Signature" static let geolocation = "GEO-Location" static let authorizationType = "Authorization-Type" + static let jwsSignature = "x-jws-signature" } public struct Headers { - public static func signedRequestHeaders(token: String, expiresAt: Int, signature: String?, appLanguage: String) -> [String: String] { - guard let signedMessage = signature else { return authorizedRequestHeaders(token: token, appLanguage: appLanguage) } + public static func signedRequestHeaders(token: String, payloadParams: [String: Any]?, connectionGuid: String) -> [String: String] { + guard let jwsSignature = SignatureHelper.sign(params: payloadParams, guid: connectionGuid) else { return [:] } - return authorizedRequestHeaders(token: token, appLanguage: appLanguage).merge( + return authorizedRequestHeaders(token: token, appLanguage: "en").merge( with: [ - HeadersKeys.expiresAt: "\(expiresAt)", - HeadersKeys.signature: "\(signedMessage)" + HeadersKeys.jwsSignature: jwsSignature ] ) } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift b/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift index a97df866..434bff24 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift @@ -38,6 +38,7 @@ struct ParametersKeys { static let authorizationCode = "authorization_code" static let credentials = "credentials" static let encryptedRsaPublicKey = "encrypted_rsa_public_key" + static let exp = "exp" } struct RequestParametersBuilder { diff --git a/SaltedgeAuthenticatorSDKv2/Classes/Routers/SEConnectionRouter.swift b/SaltedgeAuthenticatorSDKv2/Classes/Routers/SEConnectionRouter.swift index 0b805a3c..14be60d9 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/Routers/SEConnectionRouter.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/Routers/SEConnectionRouter.swift @@ -32,31 +32,41 @@ public struct SECreateConnectionParams { public let encryptedRsaPublicKey: SEEncryptedData } -// TODO: Add revoke connection case enum SEConnectionRouter: Routable { case createConnection(URL, SECreateConnectionParams, String) + case revoke(SEBaseAuthenticatedWithIdRequestData) var method: HTTPMethod { switch self { case .createConnection: return .post + case .revoke: return .delete } } var encoding: Encoding { switch self { case .createConnection: return .json + case .revoke: return .url } } var url: URL { switch self { case .createConnection(let connectUrl, _, _): return connectUrl + case .revoke(let data): + return data.url.appendingPathComponent("\(SENetPathBuilder(for: .connections).path)/\(data.entityId)/revoke") } } var headers: [String: String]? { switch self { case .createConnection(_, _, let appLanguage): return Headers.requestHeaders(with: appLanguage) + case .revoke(let data): + return Headers.signedRequestHeaders( + token: data.accessToken, + payloadParams: parameters, + connectionGuid: data.connectionGuid + ) } } @@ -64,6 +74,11 @@ enum SEConnectionRouter: Routable { switch self { case .createConnection(_, let data, _): return RequestParametersBuilder.parameters(for: data) + case .revoke: + return [ + ParametersKeys.data: {}, + ParametersKeys.exp: Date().addingTimeInterval(5.0 * 60.0).utcSeconds + ] } } } diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Helpers/ParametersSerializer.swift b/SaltedgeAuthenticatorSDKv2/Classes/SignatureHelper.swift similarity index 50% rename from SaltedgeAuthenticatorSDK/Classes/API/Helpers/ParametersSerializer.swift rename to SaltedgeAuthenticatorSDKv2/Classes/SignatureHelper.swift index e78c2989..f03ca11f 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Helpers/ParametersSerializer.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/SignatureHelper.swift @@ -1,8 +1,8 @@ // -// ParametersSerializer.swift +// SignatureHelper // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -21,18 +21,28 @@ // import Foundation +import JOSESwift +import SEAuthenticatorCore -struct ParametersSerializer { - static func createBody(parameters: [String: Any]) -> Data? { - do { - var data: Data - if #available(iOS 11.0, *) { - data = try JSONSerialization.data(withJSONObject: parameters, options: [.sortedKeys, .prettyPrinted]) - } else { - data = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted) - } +struct SignatureHelper { + static func sign(params: [String: Any]?, guid: String) -> String? { + guard let payloadParams = params, + let payloadBody = ParametersSerializer.createBody(parameters: payloadParams), + let privateKey = privateKey(for: SETagHelper.create(for: guid)), + let signer = Signer(signingAlgorithm: .RS256, key: privateKey) else { return nil } + + let header = JWSHeader(algorithm: .RS256) + + guard let jws = try? JWS(header: header, payload: Payload(payloadBody), signer: signer) else { return nil } + + let splittedSerializedJwsString = jws.compactSerializedString.split(separator: ".") - return data + return splittedSerializedJwsString[0] + ".." + splittedSerializedJwsString[2] + } + + static func privateKey(for tag: String) -> SecKey? { + do { + return try SECryptoHelper.privateKey(for: tag) } catch { print(error.localizedDescription) } diff --git a/SaltedgeAuthenticatorSDKv2/SaltedgeAuthenticatorSDKv2.podspec b/SaltedgeAuthenticatorSDKv2/SaltedgeAuthenticatorSDKv2.podspec index 0c7ce6d2..dc75e5c8 100644 --- a/SaltedgeAuthenticatorSDKv2/SaltedgeAuthenticatorSDKv2.podspec +++ b/SaltedgeAuthenticatorSDKv2/SaltedgeAuthenticatorSDKv2.podspec @@ -28,5 +28,6 @@ Pod::Spec.new do |s| s.source_files = '**/Classes/**/*' s.dependency 'SaltedgeAuthenticatorCore' - s.dependency 'CryptoSwift' +# s.dependency 'CryptoSwift' + s.dependency 'JOSESwift' end From 5105fcab9c17498b533fdde5229b5c90724cbc99 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Wed, 19 May 2021 15:45:41 +0300 Subject: [PATCH 006/110] removed unneeded files --- .../API/Models/Requests/BaseStructures.swift | 62 ------------------ .../Classes/API/Models/SEEncryptedData.swift | 63 ------------------- 2 files changed, 125 deletions(-) delete mode 100644 SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/BaseStructures.swift delete mode 100644 SaltedgeAuthenticatorSDK/Classes/API/Models/SEEncryptedData.swift diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/BaseStructures.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/BaseStructures.swift deleted file mode 100644 index 829c13e4..00000000 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/BaseStructures.swift +++ /dev/null @@ -1,62 +0,0 @@ -// -// BaseStructures.swift -// This file is part of the Salt Edge Authenticator distribution -// (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2020 Salt Edge Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 3 or later. -// -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -// For the additional permissions granted for Salt Edge Authenticator -// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md -// - -//import Foundation -// -//public class SEBaseAuthenticatedRequestData { -// public let url: URL -// public let connectionGuid: GUID -// public let accessToken: AccessToken -// public let appLanguage: ApplicationLanguage -// -// public init( -// url: URL, -// connectionGuid: GUID, -// accessToken: AccessToken, -// appLanguage: ApplicationLanguage -// ) { -// self.url = url -// self.connectionGuid = connectionGuid -// self.accessToken = accessToken -// self.appLanguage = appLanguage -// } -//} -// -//public class SEBaseAuthenticatedWithIdRequestData: SEBaseAuthenticatedRequestData { -// public let entityId: ID -// -// public init( -// url: URL, -// connectionGuid: GUID, -// accessToken: AccessToken, -// appLanguage: ApplicationLanguage, -// entityId: ID -// ) { -// self.entityId = entityId -// super.init( -// url: url, -// connectionGuid: connectionGuid, -// accessToken: accessToken, -// appLanguage: appLanguage -// ) -// } -//} diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/SEEncryptedData.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/SEEncryptedData.swift deleted file mode 100644 index 75bbb63e..00000000 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/SEEncryptedData.swift +++ /dev/null @@ -1,63 +0,0 @@ -// -// SEEncryptedData.swift -// This file is part of the Salt Edge Authenticator distribution -// (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2020 Salt Edge Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 3 or later. -// -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -// For the additional permissions granted for Salt Edge Authenticator -// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md -// - -import Foundation - -//public struct SEEncryptedData: SerializableResponse, Equatable { -// private let defaultAlgorithm = "AES-256-CBC" -// -// public let data: String -// public let key: String -// public let iv: String -// public var connectionId: String? -// -// public init(data: String, key: String, iv: String) { -// self.data = data -// self.key = key -// self.iv = iv -// } -// -// public init?(_ value: Any) { -// if let dict = value as? [String: Any], -// let data = dict[SENetKeys.data] as? String, -// let key = dict[SENetKeys.key] as? String, -// let iv = dict[SENetKeys.iv] as? String, -// let algorithm = dict[SENetKeys.algorithm] as? String, -// algorithm == defaultAlgorithm { -// self.data = data -// self.key = key -// self.iv = iv -// if let connectionId = dict[SENetKeys.connectionId] as? String { -// self.connectionId = connectionId -// } -// } else { -// return nil -// } -// } -// -// public static func == (lhs: SEEncryptedData, rhs: SEEncryptedData) -> Bool { -// return lhs.data == rhs.data && -// lhs.key == rhs.key && -// lhs.iv == rhs.iv && -// lhs.connectionId == rhs.connectionId -// } -//} From 05c8ff5613a5180f211d12f7449ffbca1e42b4c1 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Wed, 19 May 2021 15:47:03 +0300 Subject: [PATCH 007/110] moved routable to core --- .../Classes/API/Routers/Routable.swift | 74 ------------------- 1 file changed, 74 deletions(-) delete mode 100644 SaltedgeAuthenticatorSDK/Classes/API/Routers/Routable.swift diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Routers/Routable.swift b/SaltedgeAuthenticatorSDK/Classes/API/Routers/Routable.swift deleted file mode 100644 index a5370726..00000000 --- a/SaltedgeAuthenticatorSDK/Classes/API/Routers/Routable.swift +++ /dev/null @@ -1,74 +0,0 @@ -// -// Routable.swift -// This file is part of the Salt Edge Authenticator distribution -// (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 3 or later. -// -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -// For the additional permissions granted for Salt Edge Authenticator -// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md -// - -//import Foundation -//import SEAuthenticatorCore -// -//enum HTTPMethod: String { -// case get = "GET" -// case post = "POST" -// case put = "PUT" -// case delete = "DELETE" -//} -// -//enum Encoding: String { -// case url -// case json -//} -// -//protocol Routable { -// var method: HTTPMethod { get } -// var encoding: Encoding { get } -// var url: URL { get } -// var headers: [String: String]? { get } -// var parameters: [String: Any]? { get } -//} -// -//extension Routable { -// func asURLRequest() -> URLRequest { -// var request = URLRequest(url: url) -// request.httpMethod = method.rawValue -// request.allHTTPHeaderFields = headers -// -// guard let parameters = parameters else { return request } -// -// if encoding == .url { -// var components = URLComponents(url: url, resolvingAgainstBaseURL: true) -// components?.queryItems = parameters.asUrlQueryItems() -// request.url = components?.url -// } -// -// if request.value(forHTTPHeaderField: HeadersKeys.contentType) == nil { -// request.setValue("application/json", forHTTPHeaderField: HeadersKeys.contentType) -// } -// -// request.httpBody = ParametersSerializer.createBody(parameters: parameters) -// -// return request -// } -//} -// -//private extension Dictionary { -// func asUrlQueryItems() -> [URLQueryItem] { -// return map { URLQueryItem(name: "\($0.0)", value: "\($0.1)") } -// } -//} From 469489732357568f989947f4f318fcb8ddffeafd Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Wed, 19 May 2021 22:14:17 +0300 Subject: [PATCH 008/110] added JwsHelperSpec --- .../Authenticator.xcodeproj/project.pbxproj | 12 +++ Example/Tests/Spec Helpers/SpecUtils.swift | 9 +++ Example/Tests/v2/JWSHelperSpec.swift | 75 +++++++++++++++++++ .../Classes/API/Headers.swift | 2 +- ...{SignatureHelper.swift => JWSHelper.swift} | 4 +- 5 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 Example/Tests/v2/JWSHelperSpec.swift rename SaltedgeAuthenticatorSDKv2/Classes/{SignatureHelper.swift => JWSHelper.swift} (97%) diff --git a/Example/Authenticator.xcodeproj/project.pbxproj b/Example/Authenticator.xcodeproj/project.pbxproj index ffa82689..00f08458 100644 --- a/Example/Authenticator.xcodeproj/project.pbxproj +++ b/Example/Authenticator.xcodeproj/project.pbxproj @@ -111,6 +111,7 @@ 9D66E1E322D6146900BD59B6 /* ProviderResponseSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D66E1DA22D6146800BD59B6 /* ProviderResponseSpec.swift */; }; 9D66E1E522D6146900BD59B6 /* HeadersSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D66E1DC22D6146800BD59B6 /* HeadersSpec.swift */; }; 9D66E1E622D6146900BD59B6 /* RequestParametersBuilderSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D66E1DD22D6146800BD59B6 /* RequestParametersBuilderSpec.swift */; }; + 9D69071C2655439200A9CE94 /* JWSHelperSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D69071B2655439200A9CE94 /* JWSHelperSpec.swift */; }; 9D772129231E50400015BAC6 /* AuthorizationsHeaderCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D772128231E50400015BAC6 /* AuthorizationsHeaderCollectionViewCell.swift */; }; 9D77212A231E50400015BAC6 /* AuthorizationsHeaderCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D772128231E50400015BAC6 /* AuthorizationsHeaderCollectionViewCell.swift */; }; 9D77212C231E50DD0015BAC6 /* AuthorizationsHeadersSwipingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D77212B231E50DD0015BAC6 /* AuthorizationsHeadersSwipingView.swift */; }; @@ -438,6 +439,7 @@ 9D66E1DA22D6146800BD59B6 /* ProviderResponseSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProviderResponseSpec.swift; sourceTree = ""; }; 9D66E1DC22D6146800BD59B6 /* HeadersSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeadersSpec.swift; sourceTree = ""; }; 9D66E1DD22D6146800BD59B6 /* RequestParametersBuilderSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestParametersBuilderSpec.swift; sourceTree = ""; }; + 9D69071B2655439200A9CE94 /* JWSHelperSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWSHelperSpec.swift; sourceTree = ""; }; 9D772128231E50400015BAC6 /* AuthorizationsHeaderCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorizationsHeaderCollectionViewCell.swift; sourceTree = ""; }; 9D77212B231E50DD0015BAC6 /* AuthorizationsHeadersSwipingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorizationsHeadersSwipingView.swift; sourceTree = ""; }; 9D77212E231E51200015BAC6 /* AuthorizationsCollectionLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorizationsCollectionLayout.swift; sourceTree = ""; }; @@ -675,6 +677,7 @@ 607FACE81AFB9204008FA782 /* Tests */ = { isa = PBXGroup; children = ( + 9D69071A2655437E00A9CE94 /* v2 */, 9DCBB2872435F1EF00DB3F85 /* ViewModels */, 9D66E1D222D6146800BD59B6 /* Networking */, 9D66E17022D4C0AF00BD59B6 /* Errors */, @@ -897,6 +900,14 @@ path = v1; sourceTree = ""; }; + 9D69071A2655437E00A9CE94 /* v2 */ = { + isa = PBXGroup; + children = ( + 9D69071B2655439200A9CE94 /* JWSHelperSpec.swift */, + ); + path = v2; + sourceTree = ""; + }; 9DBAD79D247E55A70023F944 /* Connect */ = { isa = PBXGroup; children = ( @@ -1976,6 +1987,7 @@ 9D94095B23618CF00080EBB5 /* CacheHelper.swift in Sources */, 9DE3249022D399D200EB162A /* BiometricsPresenter.swift in Sources */, 9DD4DF2923759767000A9B80 /* AVCaptureHelper.swift in Sources */, + 9D69071C2655439200A9CE94 /* JWSHelperSpec.swift in Sources */, 9DE3249922D399ED00EB162A /* StringExtensions.swift in Sources */, 9D66E1DE22D6146900BD59B6 /* ConnectionRouterSpec.swift in Sources */, 9DE324CE22D39A9800EB162A /* CustomSpacingLabel.swift in Sources */, diff --git a/Example/Tests/Spec Helpers/SpecUtils.swift b/Example/Tests/Spec Helpers/SpecUtils.swift index b203f047..8833025a 100644 --- a/Example/Tests/Spec Helpers/SpecUtils.swift +++ b/Example/Tests/Spec Helpers/SpecUtils.swift @@ -35,6 +35,15 @@ struct SpecUtils { return connection } + static func privateKey(for tag: String) -> SecKey? { + do { + return try SECryptoHelper.privateKey(for: tag) + } catch { + print(error.localizedDescription) + } + return nil + } + static func createAuthResponse(with authMessage: [String: Any], id: ID, guid: GUID) -> SEAuthorizationData { let encryptedData = try! SECryptoHelper.encrypt(authMessage.jsonString!, tag: SETagHelper.create(for: guid)) diff --git a/Example/Tests/v2/JWSHelperSpec.swift b/Example/Tests/v2/JWSHelperSpec.swift new file mode 100644 index 00000000..31bfb9a7 --- /dev/null +++ b/Example/Tests/v2/JWSHelperSpec.swift @@ -0,0 +1,75 @@ +// +// JWSHelperSpec +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Quick +import Nimble +import JOSESwift +import SEAuthenticatorCore +@testable import SEAuthenticatorV2 + +class JWSHelperSpec: BaseSpec { + override func spec() { + let connection = SpecUtils.createConnection(id: "1") + + describe("sign") { + it("should return detached jws signature") { + let expectedMessageDict = ["data": "Test Message"] + + // create jws signature + let expectedJwsSignature = jwsSign(payload: expectedMessageDict, guid: connection.guid) + let splittedJwsSignature = expectedJwsSignature.split(separator: ".") + // get jws payload + let expectedSignaturePayload = splittedJwsSignature[1] + + // create actual jws signature + let actualSignature = JWSHelper.sign(params: expectedMessageDict, guid: connection.guid)! + let splittedActualSignature = actualSignature.split(separator: ".") + + // insert jws payload into actual jws signature + let final = splittedActualSignature[0] + ".\(expectedSignaturePayload)." + splittedActualSignature[1] + + // verify data + do { + let jws = try JWS(compactSerialization: String(final)) + let verifier = Verifier( + verifyingAlgorithm: .RS256, + publicKey: try SECryptoHelper.publicKey(for: SETagHelper.create(for: connection.guid)) + )! + let payload = try jws.validate(using: verifier).payload + let message = String(data: payload.data(), encoding: .utf8)! + + expect(message.json! == expectedMessageDict).to(beTrue()) + } + } + } + + func jwsSign(payload: [String: String], guid: String) -> String { + let payloadBody = ParametersSerializer.createBody(parameters: payload)! + let privateKey = SpecUtils.privateKey(for: SETagHelper.create(for: guid))! + let signer = Signer(signingAlgorithm: .RS256, key: privateKey)! + + let header = JWSHeader(algorithm: .RS256) + + return try! JWS(header: header, payload: Payload(payloadBody), signer: signer).compactSerializedString + } + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Headers.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Headers.swift index 2b0f60dd..5abc0def 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Headers.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Headers.swift @@ -36,7 +36,7 @@ struct HeadersKeys { public struct Headers { public static func signedRequestHeaders(token: String, payloadParams: [String: Any]?, connectionGuid: String) -> [String: String] { - guard let jwsSignature = SignatureHelper.sign(params: payloadParams, guid: connectionGuid) else { return [:] } + guard let jwsSignature = JWSHelper.sign(params: payloadParams, guid: connectionGuid) else { return [:] } return authorizedRequestHeaders(token: token, appLanguage: "en").merge( with: [ diff --git a/SaltedgeAuthenticatorSDKv2/Classes/SignatureHelper.swift b/SaltedgeAuthenticatorSDKv2/Classes/JWSHelper.swift similarity index 97% rename from SaltedgeAuthenticatorSDKv2/Classes/SignatureHelper.swift rename to SaltedgeAuthenticatorSDKv2/Classes/JWSHelper.swift index f03ca11f..539031d7 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/SignatureHelper.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/JWSHelper.swift @@ -1,5 +1,5 @@ // -// SignatureHelper +// JWSHelper // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) // Copyright © 2021 Salt Edge Inc. @@ -24,7 +24,7 @@ import Foundation import JOSESwift import SEAuthenticatorCore -struct SignatureHelper { +struct JWSHelper { static func sign(params: [String: Any]?, guid: String) -> String? { guard let payloadParams = params, let payloadBody = ParametersSerializer.createBody(parameters: payloadParams), From 60e3d789cc93d26e77222ab978ffdfc8aaf50c5d Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Thu, 20 May 2021 17:01:18 +0300 Subject: [PATCH 009/110] moved networking to core, created routers in v2 --- .../Connect/InstantActionCoordinator.swift | 1 + .../Coordinators/ConnectViewCoordinator.swift | 1 + .../Supporting Files/AppDelegate.swift | 1 + .../Utils/Handlers/ConnectHandler.swift | 1 + .../Utils/Handlers/InstantActionHandler.swift | 1 + .../Connections/ConsentViewModel.swift | 1 + Example/Podfile.lock | 3 +- .../API/Networking}/BaseNetworking.swift | 5 +- .../Classes/API/Networking}/HTTPService.swift | 15 ++-- .../Classes/API/Networking}/Networking.swift | 5 +- .../API/Networking}/URLSessionManager.swift | 4 +- .../API/Managers/SEActionManager.swift | 1 + .../API/Managers/SEProviderManager.swift | 1 + .../SECreateConnectionResponse.swift | 1 + .../Models/Responses/SEProviderResponse.swift | 1 + .../SERevokeConnectionResponse.swift | 1 + .../Responses/SERevokeConsentResponse.swift | 1 + .../Responses/SESubmitActionResponse.swift | 1 + .../API/Models/SEAuthorizationData.swift | 1 + .../Classes/API/Models/SEConsentData.swift | 1 + .../Classes/API/SEConnectHelper.swift | 1 + .../Classes/API/SENetKeys.swift | 78 ------------------- .../Classes/API/Helpers/ApiConstants.swift | 14 +++- .../API/Managers/SEProviderManager.swift | 39 ++++++++++ .../API/Responses/SEProviderResponse.swift | 60 ++++++++++++++ .../Routers/SEConnectionRouter.swift | 1 - .../API/Routers/SEProviderRouter.swift | 40 +++++++--- .../Classes/RequestParametersBuilder.swift | 2 +- 28 files changed, 169 insertions(+), 113 deletions(-) rename {SaltedgeAuthenticatorSDK/Classes/API => SaltedgeAuthenticatorCore/Classes/API/Networking}/BaseNetworking.swift (92%) rename {SaltedgeAuthenticatorSDK/Classes/API => SaltedgeAuthenticatorCore/Classes/API/Networking}/HTTPService.swift (82%) rename {SaltedgeAuthenticatorSDK/Classes/API => SaltedgeAuthenticatorCore/Classes/API/Networking}/Networking.swift (97%) rename {SaltedgeAuthenticatorSDK/Classes/API/Managers => SaltedgeAuthenticatorCore/Classes/API/Networking}/URLSessionManager.swift (95%) delete mode 100644 SaltedgeAuthenticatorSDK/Classes/API/SENetKeys.swift rename SaltedgeAuthenticatorSDK/Classes/API/SerializableResponse.swift => SaltedgeAuthenticatorSDKv2/Classes/API/Helpers/ApiConstants.swift (67%) create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEProviderManager.swift create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponse.swift rename SaltedgeAuthenticatorSDKv2/Classes/{ => API}/Routers/SEConnectionRouter.swift (98%) rename SaltedgeAuthenticatorSDK/Classes/Helpers/TypeAliases.swift => SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEProviderRouter.swift (63%) diff --git a/Example/Authenticator/Coordinators/Connect/InstantActionCoordinator.swift b/Example/Authenticator/Coordinators/Connect/InstantActionCoordinator.swift index c05d0761..0a368072 100644 --- a/Example/Authenticator/Coordinators/Connect/InstantActionCoordinator.swift +++ b/Example/Authenticator/Coordinators/Connect/InstantActionCoordinator.swift @@ -22,6 +22,7 @@ import UIKit import SEAuthenticator +import SEAuthenticatorCore final class InstantActionCoordinator: Coordinator { private var rootViewController: UIViewController diff --git a/Example/Authenticator/Coordinators/ConnectViewCoordinator.swift b/Example/Authenticator/Coordinators/ConnectViewCoordinator.swift index 5b301f71..345d7fcc 100644 --- a/Example/Authenticator/Coordinators/ConnectViewCoordinator.swift +++ b/Example/Authenticator/Coordinators/ConnectViewCoordinator.swift @@ -22,6 +22,7 @@ import UIKit import SEAuthenticator +import SEAuthenticatorCore enum ConnectionType: Equatable { case newConnection(String) diff --git a/Example/Authenticator/Supporting Files/AppDelegate.swift b/Example/Authenticator/Supporting Files/AppDelegate.swift index 839bd2bb..97e222db 100644 --- a/Example/Authenticator/Supporting Files/AppDelegate.swift +++ b/Example/Authenticator/Supporting Files/AppDelegate.swift @@ -22,6 +22,7 @@ import UIKit import UserNotifications import Firebase import SEAuthenticator +import SEAuthenticatorCore class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate { var window: UIWindow? diff --git a/Example/Authenticator/Utils/Handlers/ConnectHandler.swift b/Example/Authenticator/Utils/Handlers/ConnectHandler.swift index 7426771b..e6d2424b 100644 --- a/Example/Authenticator/Utils/Handlers/ConnectHandler.swift +++ b/Example/Authenticator/Utils/Handlers/ConnectHandler.swift @@ -22,6 +22,7 @@ import Foundation import SEAuthenticator +import SEAuthenticatorCore protocol ConnectEventsDelegate: class { func showWebViewController() diff --git a/Example/Authenticator/Utils/Handlers/InstantActionHandler.swift b/Example/Authenticator/Utils/Handlers/InstantActionHandler.swift index bc2f16c3..cb71a364 100644 --- a/Example/Authenticator/Utils/Handlers/InstantActionHandler.swift +++ b/Example/Authenticator/Utils/Handlers/InstantActionHandler.swift @@ -22,6 +22,7 @@ import UIKit import SEAuthenticator +import SEAuthenticatorCore protocol InstantActionEventsDelegate: class { func shouldPresentConnectionPicker(connections: [Connection]) diff --git a/Example/Authenticator/View Models/Connections/ConsentViewModel.swift b/Example/Authenticator/View Models/Connections/ConsentViewModel.swift index a97a476f..177e3266 100644 --- a/Example/Authenticator/View Models/Connections/ConsentViewModel.swift +++ b/Example/Authenticator/View Models/Connections/ConsentViewModel.swift @@ -22,6 +22,7 @@ import Foundation import SEAuthenticator +import SEAuthenticatorCore enum ConsentType: String { case aisp diff --git a/Example/Podfile.lock b/Example/Podfile.lock index 56837d4b..9fa61d91 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -118,7 +118,6 @@ PODS: DEPENDENCIES: - Firebase/Analytics - Firebase/Crashlytics - - JOSESwift - Nimble - Quick - ReachabilitySwift @@ -188,6 +187,6 @@ SPEC CHECKSUMS: TinyConstraints: 8dd91295e56797648c7bc335dd20e1d91ec4c192 Valet: 16d0537d70db79d9ba953b9060b5da4fb8004e51 -PODFILE CHECKSUM: bbadedc6649010c795a5f299bc2dab1a3b2654ef +PODFILE CHECKSUM: 7f15a08d33a1af46d3f787184ec65b0ed568eed4 COCOAPODS: 1.10.1 diff --git a/SaltedgeAuthenticatorSDK/Classes/API/BaseNetworking.swift b/SaltedgeAuthenticatorCore/Classes/API/Networking/BaseNetworking.swift similarity index 92% rename from SaltedgeAuthenticatorSDK/Classes/API/BaseNetworking.swift rename to SaltedgeAuthenticatorCore/Classes/API/Networking/BaseNetworking.swift index 1aba0c41..55fd08a9 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/BaseNetworking.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/Networking/BaseNetworking.swift @@ -1,8 +1,8 @@ // -// BaseNetworking.swift +// BaseNetworking // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -21,7 +21,6 @@ // import Foundation -import SEAuthenticatorCore protocol Networking { static func execute(_ urlRequest: Routable, success: @escaping RequestSuccessBlock, failure: @escaping FailureBlock) diff --git a/SaltedgeAuthenticatorSDK/Classes/API/HTTPService.swift b/SaltedgeAuthenticatorCore/Classes/API/Networking/HTTPService.swift similarity index 82% rename from SaltedgeAuthenticatorSDK/Classes/API/HTTPService.swift rename to SaltedgeAuthenticatorCore/Classes/API/Networking/HTTPService.swift index 1844a110..62c4eb19 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/HTTPService.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/Networking/HTTPService.swift @@ -1,8 +1,8 @@ // -// HTTPService.swift +// HTTPService // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -21,12 +21,13 @@ // import Foundation -import SEAuthenticatorCore -struct HTTPService { - static func execute(request: Routable, - success: @escaping HTTPServiceSuccessClosure, - failure: @escaping FailureBlock) { +public struct HTTPService { + public static func execute( + request: Routable, + success: @escaping HTTPServiceSuccessClosure, + failure: @escaping FailureBlock + ) { BaseNetworking.execute( request, success: { response in diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Networking.swift b/SaltedgeAuthenticatorCore/Classes/API/Networking/Networking.swift similarity index 97% rename from SaltedgeAuthenticatorSDK/Classes/API/Networking.swift rename to SaltedgeAuthenticatorCore/Classes/API/Networking/Networking.swift index a890c89f..1d8c1075 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Networking.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/Networking/Networking.swift @@ -1,8 +1,8 @@ // -// Networking.swift +// Networking // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -21,7 +21,6 @@ // import Foundation -import SEAuthenticatorCore extension Networking { static func execute(_ request: Routable, diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Managers/URLSessionManager.swift b/SaltedgeAuthenticatorCore/Classes/API/Networking/URLSessionManager.swift similarity index 95% rename from SaltedgeAuthenticatorSDK/Classes/API/Managers/URLSessionManager.swift rename to SaltedgeAuthenticatorCore/Classes/API/Networking/URLSessionManager.swift index ef525218..309d3ea4 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Managers/URLSessionManager.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/Networking/URLSessionManager.swift @@ -1,8 +1,8 @@ // -// URLSessionManager.swift +// URLSessionManager // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEActionManager.swift b/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEActionManager.swift index 1bff34ee..733b9a06 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEActionManager.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEActionManager.swift @@ -21,6 +21,7 @@ // import Foundation +import SEAuthenticatorCore public struct SEActionManager { public static func submitAction( diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEProviderManager.swift b/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEProviderManager.swift index 52b16810..59578933 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEProviderManager.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEProviderManager.swift @@ -21,6 +21,7 @@ // import Foundation +import SEAuthenticatorCore public struct SEProviderManager { public static func fetchProviderData( diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SECreateConnectionResponse.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SECreateConnectionResponse.swift index 2564972d..8e49fd59 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SECreateConnectionResponse.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SECreateConnectionResponse.swift @@ -21,6 +21,7 @@ // import Foundation +import SEAuthenticatorCore public struct SECreateConnectionResponse: SerializableResponse { public let id: String diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SEProviderResponse.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SEProviderResponse.swift index 0baba929..507b6194 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SEProviderResponse.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SEProviderResponse.swift @@ -21,6 +21,7 @@ // import Foundation +import SEAuthenticatorCore public struct SEProviderResponse: SerializableResponse { public let name: String diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SERevokeConnectionResponse.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SERevokeConnectionResponse.swift index 4996b0f6..71bec21a 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SERevokeConnectionResponse.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SERevokeConnectionResponse.swift @@ -21,6 +21,7 @@ // import Foundation +import SEAuthenticatorCore public struct SERevokeConnectionResponse: SerializableResponse { public let success: Bool diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SERevokeConsentResponse.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SERevokeConsentResponse.swift index 67c4c548..dd6762c9 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SERevokeConsentResponse.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SERevokeConsentResponse.swift @@ -21,6 +21,7 @@ // import Foundation +import SEAuthenticatorCore public struct SERevokeConsentResponse: SerializableResponse { public let consentId: String diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SESubmitActionResponse.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SESubmitActionResponse.swift index c48c9d6f..65993e79 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SESubmitActionResponse.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SESubmitActionResponse.swift @@ -21,6 +21,7 @@ // import Foundation +import SEAuthenticatorCore public struct SESubmitActionResponse: SerializableResponse { public let success: Bool diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/SEAuthorizationData.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/SEAuthorizationData.swift index e497a7ce..d4f194e2 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/SEAuthorizationData.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/SEAuthorizationData.swift @@ -21,6 +21,7 @@ // import Foundation +import SEAuthenticatorCore public struct SEAuthorizationData { public let id: String diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/SEConsentData.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/SEConsentData.swift index 344fd526..b25218e8 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/SEConsentData.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/SEConsentData.swift @@ -21,6 +21,7 @@ // import Foundation +import SEAuthenticatorCore public struct SEConsentData { public let id: String diff --git a/SaltedgeAuthenticatorSDK/Classes/API/SEConnectHelper.swift b/SaltedgeAuthenticatorSDK/Classes/API/SEConnectHelper.swift index 6e1fb485..28502b3a 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/SEConnectHelper.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/SEConnectHelper.swift @@ -21,6 +21,7 @@ // import Foundation +import SEAuthenticatorCore public struct SEConnectHelper { public static func isValid(deepLinkUrl url: URL) -> Bool { diff --git a/SaltedgeAuthenticatorSDK/Classes/API/SENetKeys.swift b/SaltedgeAuthenticatorSDK/Classes/API/SENetKeys.swift deleted file mode 100644 index f760692a..00000000 --- a/SaltedgeAuthenticatorSDK/Classes/API/SENetKeys.swift +++ /dev/null @@ -1,78 +0,0 @@ -// -// SENetKeys.swift -// This file is part of the Salt Edge Authenticator distribution -// (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 3 or later. -// -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -// For the additional permissions granted for Salt Edge Authenticator -// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md -// - -import Foundation - -public struct SENetKeys { - public static let aps = "aps" - - public static let data = "data" - public static let id = "id" - public static let name = "name" - public static let code = "code" - public static let logoUrl = "logo_url" - public static let supportEmail = "support_email" - public static let version = "version" - public static let geolocationRequired = "geolocation_required" - - public static let configuration = "configuration" - public static let connectQuery = "connect_query" - public static let connectUrl = "connect_url" - - public static let title = "title" - public static let description = "description" - public static let message = "message" - - public static let success = "success" - - public static let accessToken = "access_token" - - public static let createdAt = "created_at" - public static let expiresAt = "expires_at" - public static let redirectUrl = "redirect_url" - - public static let key = "key" - public static let iv = "iv" - - public static let authorizationId = "authorization_id" - public static let authorizationCode = "authorization_code" - - public static let connectionId = "connection_id" - public static let algorithm = "algorithm" - - public static let consentId = "consent_id" - - public static let errorClass = "error_class" - public static let errorMessage = "error_message" - - public static let userId = "user_id" - public static let consentManagement = "consent_management" - public static let tppName = "tpp_name" - public static let consentType = "consent_type" - public static let accounts = "accounts" - public static let sharedData = "shared_data" - public static let accountNumber = "account_number" - public static let sortCode = "sort_code" - public static let iban = "iban" - public static let balance = "balance" - public static let transactions = "transactions" -} diff --git a/SaltedgeAuthenticatorSDK/Classes/API/SerializableResponse.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Helpers/ApiConstants.swift similarity index 67% rename from SaltedgeAuthenticatorSDK/Classes/API/SerializableResponse.swift rename to SaltedgeAuthenticatorSDKv2/Classes/API/Helpers/ApiConstants.swift index 10bf556d..43f7086c 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/SerializableResponse.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Helpers/ApiConstants.swift @@ -1,8 +1,8 @@ // -// SerializableResponse.swift +// ApiConstants // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -22,6 +22,12 @@ import Foundation -public protocol SerializableResponse { - init?(_ value: Any) +struct ApiConstants { + static let scaServiceUrl = "sca_service_url" + static let apiVersion = "api_version" + static let providerId = "provider_id" + static let providerName = "provider_name" + static let providerLogoUrl = "provider_logo_url" + static let providerSupportEmail = "provider_support_email" + static let providerPublicKey = "provider_public_key" } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEProviderManager.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEProviderManager.swift new file mode 100644 index 00000000..70bec8ad --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEProviderManager.swift @@ -0,0 +1,39 @@ +// +// SEProviderManager +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +public struct SEProviderManager { + public static func fetchProviderData( + url: URL, + onSuccess success: @escaping HTTPServiceSuccessClosure, + onFailure failure: @escaping FailureBlock + ) { + HTTPService.execute( + request: SEProviderRouter.fetchData(url), + success: success, + failure: failure + ) + } +} + diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponse.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponse.swift new file mode 100644 index 00000000..05df83a2 --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponse.swift @@ -0,0 +1,60 @@ +// +// SEProviderResponse +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +public struct SEProviderResponse: SerializableResponse { + public let id: String + public let name: String + public let scaServiceUrl: String + public let apiVersion: String + public var logoUrl: URL? + public var supportEmail: String + public var publicKey: String + public let geolocationRequired: Bool? + + public init?(_ value: Any) { + if let dict = value as? [String: Any], + let dataDict = dict[SENetKeys.data] as? [String: Any], + let id = dataDict[SENetKeys.id] as? String, + let name = dataDict[ApiConstants.providerName] as? String, + let scaServiceUrl = dataDict[ApiConstants.scaServiceUrl] as? String, + let apiVersion = dataDict[ApiConstants.apiVersion] as? String, + let publicKey = dataDict[ApiConstants.providerPublicKey] as? String { + if let logoUrlString = dataDict[ApiConstants.providerLogoUrl] as? String, + let logoUrl = URL(string: logoUrlString) { + self.logoUrl = logoUrl + } + self.supportEmail = (dataDict[ApiConstants.providerSupportEmail] as? String) ?? "" + self.geolocationRequired = dataDict[SENetKeys.geolocationRequired] as? Bool + + self.id = id + self.name = name + self.scaServiceUrl = scaServiceUrl + self.apiVersion = apiVersion + self.publicKey = publicKey + } else { + return nil + } + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/Routers/SEConnectionRouter.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConnectionRouter.swift similarity index 98% rename from SaltedgeAuthenticatorSDKv2/Classes/Routers/SEConnectionRouter.swift rename to SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConnectionRouter.swift index 14be60d9..d0c64f77 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/Routers/SEConnectionRouter.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConnectionRouter.swift @@ -26,7 +26,6 @@ import SEAuthenticatorCore public struct SECreateConnectionParams { public let providerId: String public let returnUrl: String - public let platform: String public let pushToken: String? public let connectQuery: String? public let encryptedRsaPublicKey: SEEncryptedData diff --git a/SaltedgeAuthenticatorSDK/Classes/Helpers/TypeAliases.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEProviderRouter.swift similarity index 63% rename from SaltedgeAuthenticatorSDK/Classes/Helpers/TypeAliases.swift rename to SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEProviderRouter.swift index 6dae3fd3..5583744c 100644 --- a/SaltedgeAuthenticatorSDK/Classes/Helpers/TypeAliases.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEProviderRouter.swift @@ -1,8 +1,8 @@ // -// TypeAliases.swift +// SEProviderRouter // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -20,15 +20,31 @@ // under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md // -public typealias SuccessBlock = () -> () -public typealias FailureBlock = (String) -> () -public typealias RequestSuccessBlock = ([String: Any]?) -> () -public typealias HTTPServiceSuccessClosure = (T) -> () +import Foundation +import SEAuthenticatorCore -public typealias AccessToken = String -public typealias GUID = String -public typealias ID = String +enum SEProviderRouter: Routable { + case fetchData(URL) -public typealias PushToken = String -public typealias ConnectQuery = String -public typealias ApplicationLanguage = String + var method: HTTPMethod { + return .get + } + + var encoding: Encoding { + return .url + } + + var url: URL { + switch self { + case .fetchData(let url): return url + } + } + + var headers: [String: String]? { + return nil + } + + var parameters: [String: Any]? { + return nil + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift b/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift index 434bff24..22fc8ac7 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift @@ -53,7 +53,7 @@ struct RequestParametersBuilder { ParametersKeys.data: [ ParametersKeys.providerId: connectionParams.providerId, ParametersKeys.returnUrl: SENetConstants.oauthRedirectUrl, - ParametersKeys.platform: connectionParams.platform, + ParametersKeys.platform: "ios", ParametersKeys.pushToken: connectionParams.pushToken, ParametersKeys.encryptedRsaPublicKey: encryptedRsaPublicKeyDict, ParametersKeys.connectQuery: connectionParams.connectQuery From 73a1629067ec236b46a4ccc4134406876bd207ba Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Fri, 21 May 2021 14:31:04 +0300 Subject: [PATCH 010/110] added connection manager and responses --- .../Classes/API/Helpers/ApiConstants.swift | 2 + .../API/Managers/SEConnectionManager.swift | 52 +++++++++++++++++++ .../SECreateConnectionResponse.swift | 41 +++++++++++++++ .../SERevokeConnectionResponse.swift | 38 ++++++++++++++ 4 files changed, 133 insertions(+) create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConnectionManager.swift create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SECreateConnectionResponse.swift create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConnectionResponse.swift diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Helpers/ApiConstants.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Helpers/ApiConstants.swift index 43f7086c..b2785ce4 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Helpers/ApiConstants.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Helpers/ApiConstants.swift @@ -30,4 +30,6 @@ struct ApiConstants { static let providerLogoUrl = "provider_logo_url" static let providerSupportEmail = "provider_support_email" static let providerPublicKey = "provider_public_key" + + static let authenticationUrl = "authentication_url" } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConnectionManager.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConnectionManager.swift new file mode 100644 index 00000000..e4d9d650 --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConnectionManager.swift @@ -0,0 +1,52 @@ +// +// SEConnectionManager +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +public struct SEConnectionManager { + public static func createConnection( + by url: URL, + params: SECreateConnectionParams, + appLanguage: ApplicationLanguage, + onSuccess success: @escaping HTTPServiceSuccessClosure, + onFailure failure: @escaping FailureBlock + ) { + HTTPService.execute( + request: SEConnectionRouter.createConnection(url, params, appLanguage), + success: success, + failure: failure + ) + } + + public static func revokeConnection( + data: SEBaseAuthenticatedWithIdRequestData, + onSuccess success: @escaping HTTPServiceSuccessClosure, + onFailure failure: @escaping FailureBlock + ) { + HTTPService.execute( + request: SEConnectionRouter.revoke(data), + success: success, + failure: failure + ) + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SECreateConnectionResponse.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SECreateConnectionResponse.swift new file mode 100644 index 00000000..865b807f --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SECreateConnectionResponse.swift @@ -0,0 +1,41 @@ +// +// SECreateConnectionResponse +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +public struct SECreateConnectionResponse: SerializableResponse { + public let id: String + public let authenticationUrl: String + + public init?(_ value: Any) { + if let dict = value as? [String: Any], + let dataDict = dict[SENetKeys.data] as? [String: Any], + let id = dataDict[SENetKeys.connectionId] as? String, + let authenticationUrl = dataDict[ApiConstants.authenticationUrl] as? String { + self.id = id + self.authenticationUrl = authenticationUrl + } else { + return nil + } + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConnectionResponse.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConnectionResponse.swift new file mode 100644 index 00000000..220267fb --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConnectionResponse.swift @@ -0,0 +1,38 @@ +// +// SERevokeConnectionResponse +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +public struct SERevokeConnectionResponse: SerializableResponse { + public let connectionId: String + + public init?(_ value: Any) { + if let dict = value as? [String: Any], + let dataDict = dict[SENetKeys.data] as? [String: Any], + let connectionId = dataDict[SENetKeys.connectionId] as? String { + self.connectionId = connectionId + } else { + return nil + } + } +} From 51551b24674cf78edc1652ffdb3844802425ee0e Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Fri, 21 May 2021 17:51:15 +0300 Subject: [PATCH 011/110] added specs and fixed v2 routers --- .../Authenticator.xcodeproj/project.pbxproj | 28 ++++ .../Spec Helpers/URLRequestBuilder.swift | 12 +- .../RequestParametersBuilderSpecV2.swift | 54 +++++++ .../Routers/SEAuthorizationRouterSpecV2.swift | 140 ++++++++++++++++++ .../Routers/SEConnectionRouterSpecV2.swift | 99 +++++++++++++ .../Classes/API/Headers.swift | 2 +- .../Classes/API/Helpers/ApiConstants.swift | 2 + .../API/Routers/SEAuthorizationRouter.swift | 110 ++++++++++++++ .../API/Routers/SEConnectionRouter.swift | 9 +- .../Classes/RequestParametersBuilder.swift | 17 ++- 10 files changed, 458 insertions(+), 15 deletions(-) create mode 100644 Example/Tests/v2/Networking/RequestParametersBuilderSpecV2.swift create mode 100644 Example/Tests/v2/Networking/Routers/SEAuthorizationRouterSpecV2.swift create mode 100644 Example/Tests/v2/Networking/Routers/SEConnectionRouterSpecV2.swift create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEAuthorizationRouter.swift diff --git a/Example/Authenticator.xcodeproj/project.pbxproj b/Example/Authenticator.xcodeproj/project.pbxproj index 00f08458..a9aa10dc 100644 --- a/Example/Authenticator.xcodeproj/project.pbxproj +++ b/Example/Authenticator.xcodeproj/project.pbxproj @@ -112,6 +112,9 @@ 9D66E1E522D6146900BD59B6 /* HeadersSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D66E1DC22D6146800BD59B6 /* HeadersSpec.swift */; }; 9D66E1E622D6146900BD59B6 /* RequestParametersBuilderSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D66E1DD22D6146800BD59B6 /* RequestParametersBuilderSpec.swift */; }; 9D69071C2655439200A9CE94 /* JWSHelperSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D69071B2655439200A9CE94 /* JWSHelperSpec.swift */; }; + 9D6907402657E38A00A9CE94 /* RequestParametersBuilderSpecV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D69073F2657E38A00A9CE94 /* RequestParametersBuilderSpecV2.swift */; }; + 9D6907432657E67C00A9CE94 /* SEConnectionRouterSpecV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D6907422657E67C00A9CE94 /* SEConnectionRouterSpecV2.swift */; }; + 9D6907452657F94C00A9CE94 /* SEAuthorizationRouterSpecV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D6907442657F94C00A9CE94 /* SEAuthorizationRouterSpecV2.swift */; }; 9D772129231E50400015BAC6 /* AuthorizationsHeaderCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D772128231E50400015BAC6 /* AuthorizationsHeaderCollectionViewCell.swift */; }; 9D77212A231E50400015BAC6 /* AuthorizationsHeaderCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D772128231E50400015BAC6 /* AuthorizationsHeaderCollectionViewCell.swift */; }; 9D77212C231E50DD0015BAC6 /* AuthorizationsHeadersSwipingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D77212B231E50DD0015BAC6 /* AuthorizationsHeadersSwipingView.swift */; }; @@ -440,6 +443,9 @@ 9D66E1DC22D6146800BD59B6 /* HeadersSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeadersSpec.swift; sourceTree = ""; }; 9D66E1DD22D6146800BD59B6 /* RequestParametersBuilderSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestParametersBuilderSpec.swift; sourceTree = ""; }; 9D69071B2655439200A9CE94 /* JWSHelperSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWSHelperSpec.swift; sourceTree = ""; }; + 9D69073F2657E38A00A9CE94 /* RequestParametersBuilderSpecV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestParametersBuilderSpecV2.swift; sourceTree = ""; }; + 9D6907422657E67C00A9CE94 /* SEConnectionRouterSpecV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SEConnectionRouterSpecV2.swift; sourceTree = ""; }; + 9D6907442657F94C00A9CE94 /* SEAuthorizationRouterSpecV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SEAuthorizationRouterSpecV2.swift; sourceTree = ""; }; 9D772128231E50400015BAC6 /* AuthorizationsHeaderCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorizationsHeaderCollectionViewCell.swift; sourceTree = ""; }; 9D77212B231E50DD0015BAC6 /* AuthorizationsHeadersSwipingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorizationsHeadersSwipingView.swift; sourceTree = ""; }; 9D77212E231E51200015BAC6 /* AuthorizationsCollectionLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorizationsCollectionLayout.swift; sourceTree = ""; }; @@ -903,11 +909,30 @@ 9D69071A2655437E00A9CE94 /* v2 */ = { isa = PBXGroup; children = ( + 9D69073E2657E37000A9CE94 /* Networking */, 9D69071B2655439200A9CE94 /* JWSHelperSpec.swift */, ); path = v2; sourceTree = ""; }; + 9D69073E2657E37000A9CE94 /* Networking */ = { + isa = PBXGroup; + children = ( + 9D6907412657E66500A9CE94 /* Routers */, + 9D69073F2657E38A00A9CE94 /* RequestParametersBuilderSpecV2.swift */, + ); + path = Networking; + sourceTree = ""; + }; + 9D6907412657E66500A9CE94 /* Routers */ = { + isa = PBXGroup; + children = ( + 9D6907422657E67C00A9CE94 /* SEConnectionRouterSpecV2.swift */, + 9D6907442657F94C00A9CE94 /* SEAuthorizationRouterSpecV2.swift */, + ); + path = Routers; + sourceTree = ""; + }; 9DBAD79D247E55A70023F944 /* Connect */ = { isa = PBXGroup; children = ( @@ -1926,6 +1951,7 @@ 9DE324D022D39A9F00EB162A /* ModalView.swift in Sources */, 959E30B0247BDA250067354A /* PasscodeViewModelSpec.swift in Sources */, 9DE3249822D399EB00EB162A /* FontsExtensions.swift in Sources */, + 9D6907432657E67C00A9CE94 /* SEConnectionRouterSpecV2.swift in Sources */, 9DE324B322D39A3700EB162A /* ParametersSerializer.swift in Sources */, 9DE324AF22D39A2D00EB162A /* PasscodeManger.swift in Sources */, 9581A9D92477D63C0066EF5E /* WKWebViewController.swift in Sources */, @@ -1938,6 +1964,7 @@ 9DE324B622D39A4700EB162A /* SettingsViewController.swift in Sources */, 959E3098247BBA190067354A /* SettingsViewModel.swift in Sources */, 9DFD634923BF366C0060B29F /* AuthorizationStateView.swift in Sources */, + 9D6907402657E38A00A9CE94 /* RequestParametersBuilderSpecV2.swift in Sources */, 9DE324A522D39A1300EB162A /* Constants.swift in Sources */, 9D4310F622D7662C004A7FCF /* AboutViewController.swift in Sources */, 9D6575FB232A9E8100DCC53E /* DateUtilsSpec.swift in Sources */, @@ -1959,6 +1986,7 @@ 9DE3247B22D3998200EB162A /* AuthorizationsCoordinator.swift in Sources */, 9DE308BC2446287A009EBD39 /* PasscodeViewController.swift in Sources */, 9D66E1A922D4C1C100BD59B6 /* AuthorizationDataSpec.swift in Sources */, + 9D6907452657F94C00A9CE94 /* SEAuthorizationRouterSpecV2.swift in Sources */, 9DE324DC22D39AC100EB162A /* TaptileFeedbackButton.swift in Sources */, 9D453CC223CCB56400702695 /* main.swift in Sources */, 9DE324AB22D39A2300EB162A /* Localizations.swift in Sources */, diff --git a/Example/Tests/Spec Helpers/URLRequestBuilder.swift b/Example/Tests/Spec Helpers/URLRequestBuilder.swift index 1bafce52..1dacee0b 100644 --- a/Example/Tests/Spec Helpers/URLRequestBuilder.swift +++ b/Example/Tests/Spec Helpers/URLRequestBuilder.swift @@ -25,11 +25,13 @@ import SEAuthenticatorCore @testable import SEAuthenticator struct URLRequestBuilder { - static func buildUrlRequest(with url: URL, - method: String, - headers: [String: String]? = nil, - params: [String: Any]? = nil, - encoding: Encoding = .json) -> URLRequest { + static func buildUrlRequest( + with url: URL, + method: String, + headers: [String: String]? = nil, + params: [String: Any]? = nil, + encoding: Encoding = .json + ) -> URLRequest { var request = URLRequest(url: url) request.httpMethod = method request.allHTTPHeaderFields = headers diff --git a/Example/Tests/v2/Networking/RequestParametersBuilderSpecV2.swift b/Example/Tests/v2/Networking/RequestParametersBuilderSpecV2.swift new file mode 100644 index 00000000..18d9aa55 --- /dev/null +++ b/Example/Tests/v2/Networking/RequestParametersBuilderSpecV2.swift @@ -0,0 +1,54 @@ +// +// RequestParametersBuilderSpec +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Quick +import Nimble +import SEAuthenticatorCore +@testable import SEAuthenticatorV2 + +class RequestParametersBuilderSpecV2: BaseSpec { + override func spec() { + describe("confirmAuthorizationParams(for:)") { + it("should return parameters from providerData to obtain token") { + let encryptedData = SEEncryptedData(data: "data", key: "key", iv: "iv") + let expirationTime = Date().addingTimeInterval(5.0 * 60.0).utcSeconds + + let expectedParams: [String: Any] = [ + ParametersKeys.data: [ + ParametersKeys.data: encryptedData.data, + ParametersKeys.key: encryptedData.key, + ParametersKeys.iv: encryptedData.iv + ], + ParametersKeys.exp: expirationTime + ] + + let result = RequestParametersBuilder.confirmAuthorizationParams( + encryptedData: encryptedData, + exp: expirationTime + ) == expectedParams + + expect(result).to(beTruthy()) + } + } + } +} + diff --git a/Example/Tests/v2/Networking/Routers/SEAuthorizationRouterSpecV2.swift b/Example/Tests/v2/Networking/Routers/SEAuthorizationRouterSpecV2.swift new file mode 100644 index 00000000..bc9b6c64 --- /dev/null +++ b/Example/Tests/v2/Networking/Routers/SEAuthorizationRouterSpecV2.swift @@ -0,0 +1,140 @@ +// +// SEAuthorizationRouterSpecV2 +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Quick +import Nimble +import SEAuthenticatorCore +@testable import SEAuthenticatorV2 + +class SEAuthorizationRouterSpecV2: BaseSpec { + override func spec() { + let baseUrl = URL(string: "https://baseUrl.com")! + let baseUrlPath = "/api/authenticator/v2/authorizations" + let accessToken = "access_token" + + describe("AuthorizationsRouter") { + context("when it's .list") { + it("should create a valid url request") { + let expectedRequest = URLRequestBuilder.buildUrlRequest( + with: baseUrl.appendingPathComponent(baseUrlPath), + method: HTTPMethod.get.rawValue, + headers: Headers.authorizedRequestHeaders(token: accessToken), + encoding: .url + ) + + let request = SEAuthorizationRouter.list(baseUrl, accessToken).asURLRequest() + + expect(request).to(equal(expectedRequest)) + } + } + + context("when it's .show") { + it("should create a valid url request") { + let data = SEBaseAuthenticatedWithIdRequestData( + url: baseUrl, + connectionGuid: "123guid", + accessToken: accessToken, + appLanguage: "en", + entityId: "1" + ) + + let expectedRequest = URLRequestBuilder.buildUrlRequest( + with: baseUrl.appendingPathComponent("\(baseUrlPath)/\(data.entityId)"), + method: HTTPMethod.get.rawValue, + headers: Headers.authorizedRequestHeaders(token: accessToken), + encoding: .url + ) + + let request = SEAuthorizationRouter.show(data).asURLRequest() + + expect(request).to(equal(expectedRequest)) + } + } + + context("when it's .confirm") { + it("should create a valid url request") { + let data = SEAuthorizationRequestData( + id: "1", + authorizationCode: "authorization_code", + userAuthorizationType: "passcode", + geolocation: "GEO:", + connectionGuid: "123guid" + ) + let parameters = RequestParametersBuilder.confirmAuthorizationParams( + encryptedData: data.encryptedData, + exp: Date().addingTimeInterval(5.0 * 60.0).utcSeconds + ) + let headers = Headers.signedRequestHeaders( + token: accessToken, + payloadParams: parameters, + connectionGuid: "123guid" + ) + + let expectedRequest = URLRequestBuilder.buildUrlRequest( + with: baseUrl.appendingPathComponent("\(baseUrlPath)/\(data.id)/confirm"), + method: HTTPMethod.put.rawValue, + headers: headers, + params: parameters, + encoding: .json + ) + + let request = SEAuthorizationRouter.confirm(baseUrl, accessToken, data).asURLRequest() + + expect(request).to(equal(expectedRequest)) + } + } + + context("when it's .deny") { + it("should create a valid url request") { + let data = SEAuthorizationRequestData( + id: "1", + authorizationCode: "authorization_code", + userAuthorizationType: "passcode", + geolocation: "GEO:", + connectionGuid: "123guid" + ) + let parameters = RequestParametersBuilder.confirmAuthorizationParams( + encryptedData: data.encryptedData, + exp: Date().addingTimeInterval(5.0 * 60.0).utcSeconds + ) + let headers = Headers.signedRequestHeaders( + token: accessToken, + payloadParams: parameters, + connectionGuid: "123guid" + ) + + let expectedRequest = URLRequestBuilder.buildUrlRequest( + with: baseUrl.appendingPathComponent("\(baseUrlPath)/\(data.id)/deny"), + method: HTTPMethod.put.rawValue, + headers: headers, + params: parameters, + encoding: .json + ) + + let request = SEAuthorizationRouter.deny(baseUrl, accessToken, data).asURLRequest() + + expect(request).to(equal(expectedRequest)) + } + } + } + } +} diff --git a/Example/Tests/v2/Networking/Routers/SEConnectionRouterSpecV2.swift b/Example/Tests/v2/Networking/Routers/SEConnectionRouterSpecV2.swift new file mode 100644 index 00000000..b71dcd70 --- /dev/null +++ b/Example/Tests/v2/Networking/Routers/SEConnectionRouterSpecV2.swift @@ -0,0 +1,99 @@ +// +// SEConnectionRouterSpecV2 +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Quick +import Nimble +import SEAuthenticatorCore +@testable import SEAuthenticatorV2 + +class SEConnectionRouterSpecV2: BaseSpec { + override func spec() { + let baseUrl = URL(string: "https://baseUrl.com")! + let baseUrlPath = "/api/authenticator/v2/connections" + + describe("ConnectionRouter") { + context(".createConnection") { + it("should create a valid url request") { + let encryptedData = SEEncryptedData(data: "data", key: "key", iv: "iv") + let data = SECreateConnectionParams( + providerId: "12", + returnUrl: "return.com", + pushToken: "push_token", + connectQuery: "connect_query", + encryptedRsaPublicKey: encryptedData + ) + + let expectedRequest = URLRequestBuilder.buildUrlRequest( + with: baseUrl.appendingPathComponent(baseUrlPath), + method: HTTPMethod.post.rawValue, + headers: Headers.requestHeaders(with: "en"), + params: RequestParametersBuilder.parameters(for: data), + encoding: .json + ) + + let actualRequest = SEConnectionRouter.createConnection(baseUrl, data, "en").asURLRequest() + + expect(actualRequest).to(equal(expectedRequest)) + } + } + + context(".revoke") { + it("should create a valid url request") { + let connectionGuid = "guid" + let entityId = "1" + let accessToken = "access_token" + let parameters = [ + ParametersKeys.data: [:], + ParametersKeys.exp: Date().addingTimeInterval(5.0 * 60.0).utcSeconds + ] as [String : Any] + + let headers = Headers.signedRequestHeaders( + token: accessToken, + payloadParams: parameters, + connectionGuid: connectionGuid + ) + + let expectedRequest = URLRequestBuilder.buildUrlRequest( + with: baseUrl.appendingPathComponent("/api/authenticator/v2/connections/\(entityId)/revoke"), + method: HTTPMethod.delete.rawValue, + headers: headers, + params: parameters, + encoding: .url + ) + + let actualRequest = SEConnectionRouter.revoke( + SEBaseAuthenticatedWithIdRequestData( + url: baseUrl, + connectionGuid: connectionGuid, + accessToken: accessToken, + appLanguage: "en", + entityId: entityId + ) + ).asURLRequest() + + expect(actualRequest).to(equal(expectedRequest)) + } + } + } + } +} + diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Headers.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Headers.swift index 5abc0def..2f08359e 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Headers.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Headers.swift @@ -45,7 +45,7 @@ public struct Headers { ) } - public static func authorizedRequestHeaders(token: String, appLanguage: String) -> [String: String] { + public static func authorizedRequestHeaders(token: String, appLanguage: String = "en") -> [String: String] { return requestHeaders(with: appLanguage).merge(with: [HeadersKeys.accessToken: token]) } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Helpers/ApiConstants.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Helpers/ApiConstants.swift index b2785ce4..05fc3233 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Helpers/ApiConstants.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Helpers/ApiConstants.swift @@ -32,4 +32,6 @@ struct ApiConstants { static let providerPublicKey = "provider_public_key" static let authenticationUrl = "authentication_url" + static let userAuthorizationType = "user_authorization_type" + static let geolocation = "geolocation" } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEAuthorizationRouter.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEAuthorizationRouter.swift new file mode 100644 index 00000000..fbdafdc5 --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEAuthorizationRouter.swift @@ -0,0 +1,110 @@ +// +// SEAuthorizationRouter +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore +import CryptoSwift + +public struct SEAuthorizationRequestData { + public let id: ID + public let authorizationCode: String + public let userAuthorizationType: String + public let geolocation: String + public let connectionGuid: GUID + + public var encryptedData: SEEncryptedData? { + guard let data = [ + SENetKeys.authorizationCode: authorizationCode, + ApiConstants.userAuthorizationType: userAuthorizationType, + ApiConstants.geolocation: geolocation + ].jsonString else { return nil } + + return try? SECryptoHelper.encrypt(data, tag: SETagHelper.create(for: connectionGuid)) + } +} + +enum SEAuthorizationRouter: Routable { + case list(URL, AccessToken) + case show(SEBaseAuthenticatedWithIdRequestData) + case confirm(URL, AccessToken, SEAuthorizationRequestData) + case deny(URL, AccessToken, SEAuthorizationRequestData) + + var method: HTTPMethod { + switch self { + case .list, .show: return .get + case .confirm, .deny: return .put + } + } + + var encoding: Encoding { + switch self { + case .list, .show: return .url + case .confirm, .deny: return .json + } + } + + var url: URL { + switch self { + case .list(let url, _): return url.appendingPathComponent( + "\(SENetPathBuilder(for: .authorizations, version: 2).path)" + ) + case .show(let data): + return data.url.appendingPathComponent( + "\(SENetPathBuilder(for: .authorizations, version: 2).path)/\(data.entityId)" + ) + case .confirm(let url, _, let data): + return url.appendingPathComponent( + "\(SENetPathBuilder(for: .authorizations, version: 2).path)/\(data.id)/confirm" + ) + case .deny(let url, _, let data): + return url.appendingPathComponent( + "\(SENetPathBuilder(for: .authorizations, version: 2).path)/\(data.id)/deny" + ) + } + } + + var headers: [String : String]? { + switch self { + case .list(_, let accessToken): + return Headers.authorizedRequestHeaders(token: accessToken) + case .show(let data): + return Headers.authorizedRequestHeaders(token: data.accessToken) + case .confirm(_, let accessToken, let data), .deny(_, let accessToken, let data): + return Headers.signedRequestHeaders( + token: accessToken, + payloadParams: parameters, + connectionGuid: data.connectionGuid + ) + } + } + + var parameters: [String : Any]? { + switch self { + case .list, .show: return nil + case .confirm(_, _, let data), .deny(_, _, let data): + return RequestParametersBuilder.confirmAuthorizationParams( + encryptedData: data.encryptedData, + exp: Date().addingTimeInterval(5.0 * 60.0).utcSeconds + ) + } + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConnectionRouter.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConnectionRouter.swift index d0c64f77..6d17a6d2 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConnectionRouter.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConnectionRouter.swift @@ -51,9 +51,12 @@ enum SEConnectionRouter: Routable { var url: URL { switch self { - case .createConnection(let connectUrl, _, _): return connectUrl + case .createConnection(let connectUrl, _, _): + return connectUrl.appendingPathComponent(SENetPathBuilder(for: .connections, version: 2).path) case .revoke(let data): - return data.url.appendingPathComponent("\(SENetPathBuilder(for: .connections).path)/\(data.entityId)/revoke") + return data.url.appendingPathComponent( + "\(SENetPathBuilder(for: .connections, version: 2).path)/\(data.entityId)/revoke" + ) } } @@ -75,7 +78,7 @@ enum SEConnectionRouter: Routable { return RequestParametersBuilder.parameters(for: data) case .revoke: return [ - ParametersKeys.data: {}, + ParametersKeys.data: [:], ParametersKeys.exp: Date().addingTimeInterval(5.0 * 60.0).utcSeconds ] } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift b/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift index 22fc8ac7..bc46816b 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift @@ -61,13 +61,18 @@ struct RequestParametersBuilder { ] } - static func confirmAuthorization(_ confirm: Bool, authorizationCode: String?) -> [String: Any] { - var data: [String: Any] = [ParametersKeys.confirm: confirm] + static func confirmAuthorizationParams(encryptedData: SEEncryptedData?, exp: Int) -> [String: Any] { + guard let encryptedData = encryptedData else { return [:] } - if let authorizationCode = authorizationCode { - data = data.merge(with: [ParametersKeys.authorizationCode: authorizationCode]) - } + let encryptedDataParams = [ + SENetKeys.data: encryptedData.data, + SENetKeys.key: encryptedData.key, + SENetKeys.iv: encryptedData.iv + ] - return [ParametersKeys.data: data] + return [ + ParametersKeys.data: encryptedDataParams, + ParametersKeys.exp: exp + ] } } From a46fbb626953b249e609a427d00d654bc53e9a9b Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Mon, 24 May 2021 12:42:41 +0300 Subject: [PATCH 012/110] fixed specs --- .../Authenticator.xcodeproj/project.pbxproj | 6 --- .../Utils/Helpers/ParametersSerializer.swift | 37 ------------------- .../Spec Helpers/URLRequestBuilder.swift | 1 - .../Routers/SEConnectionRouterSpecV2.swift | 7 ++-- .../API/Routers/SEConnectionRouter.swift | 3 +- 5 files changed, 4 insertions(+), 50 deletions(-) delete mode 100644 Example/Authenticator/Utils/Helpers/ParametersSerializer.swift diff --git a/Example/Authenticator.xcodeproj/project.pbxproj b/Example/Authenticator.xcodeproj/project.pbxproj index a9aa10dc..ab46d99b 100644 --- a/Example/Authenticator.xcodeproj/project.pbxproj +++ b/Example/Authenticator.xcodeproj/project.pbxproj @@ -192,7 +192,6 @@ 9DCED8E722CE18280050ED3C /* PasscodeManger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED83D22CE18270050ED3C /* PasscodeManger.swift */; }; 9DCED8E922CE18280050ED3C /* NotificationsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED83F22CE18270050ED3C /* NotificationsManager.swift */; }; 9DCED8EA22CE18280050ED3C /* HapticFeedbackHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED84022CE18270050ED3C /* HapticFeedbackHelper.swift */; }; - 9DCED8EB22CE18280050ED3C /* ParametersSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED84122CE18270050ED3C /* ParametersSerializer.swift */; }; 9DCED8EC22CE18280050ED3C /* BiometricsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED84222CE18270050ED3C /* BiometricsHelper.swift */; }; 9DCED8ED22CE18280050ED3C /* AuthorizationDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED84422CE18270050ED3C /* AuthorizationDetailViewModel.swift */; }; 9DCED8EE22CE18280050ED3C /* MessageBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED84622CE18270050ED3C /* MessageBarView.swift */; }; @@ -306,7 +305,6 @@ 9DE324AF22D39A2D00EB162A /* PasscodeManger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED83D22CE18270050ED3C /* PasscodeManger.swift */; }; 9DE324B122D39A3200EB162A /* NotificationsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED83F22CE18270050ED3C /* NotificationsManager.swift */; }; 9DE324B222D39A3500EB162A /* HapticFeedbackHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED84022CE18270050ED3C /* HapticFeedbackHelper.swift */; }; - 9DE324B322D39A3700EB162A /* ParametersSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED84122CE18270050ED3C /* ParametersSerializer.swift */; }; 9DE324B422D39A3A00EB162A /* BiometricsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED84222CE18270050ED3C /* BiometricsHelper.swift */; }; 9DE324B622D39A4700EB162A /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED7E122CE18270050ED3C /* SettingsViewController.swift */; }; 9DE324B822D39A4E00EB162A /* ConnectionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED7E422CE18270050ED3C /* ConnectionsViewController.swift */; }; @@ -509,7 +507,6 @@ 9DCED83D22CE18270050ED3C /* PasscodeManger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeManger.swift; sourceTree = ""; }; 9DCED83F22CE18270050ED3C /* NotificationsManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationsManager.swift; sourceTree = ""; }; 9DCED84022CE18270050ED3C /* HapticFeedbackHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HapticFeedbackHelper.swift; sourceTree = ""; }; - 9DCED84122CE18270050ED3C /* ParametersSerializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParametersSerializer.swift; sourceTree = ""; }; 9DCED84222CE18270050ED3C /* BiometricsHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BiometricsHelper.swift; sourceTree = ""; }; 9DCED84422CE18270050ED3C /* AuthorizationDetailViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationDetailViewModel.swift; sourceTree = ""; }; 9DCED84622CE18270050ED3C /* MessageBarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageBarView.swift; sourceTree = ""; }; @@ -1121,7 +1118,6 @@ 9DD8945F22E9F4BD008F3130 /* NotificationsHelper.swift */, 9DCED83F22CE18270050ED3C /* NotificationsManager.swift */, 9DCED84022CE18270050ED3C /* HapticFeedbackHelper.swift */, - 9DCED84122CE18270050ED3C /* ParametersSerializer.swift */, 9DCED84222CE18270050ED3C /* BiometricsHelper.swift */, 9DD4DF242375969A000A9B80 /* QuickActionsHelper.swift */, 9DCBB28A243618B900DB3F85 /* Observable.swift */, @@ -1876,7 +1872,6 @@ 9DCED89C22CE18280050ED3C /* SettingsViewController.swift in Sources */, 9DCED8D122CE18280050ED3C /* BundleExtensions.swift in Sources */, 95C1EFC522D5ECAE00078A28 /* AboutViewController.swift in Sources */, - 9DCED8EB22CE18280050ED3C /* ParametersSerializer.swift in Sources */, 9DCED8DC22CE18280050ED3C /* LocalizationHelper.swift in Sources */, 9DCED92622CE18280050ED3C /* AppDelegate.swift in Sources */, 9D98747024B353A300EE89BB /* ConsentExpirationView.swift in Sources */, @@ -1952,7 +1947,6 @@ 959E30B0247BDA250067354A /* PasscodeViewModelSpec.swift in Sources */, 9DE3249822D399EB00EB162A /* FontsExtensions.swift in Sources */, 9D6907432657E67C00A9CE94 /* SEConnectionRouterSpecV2.swift in Sources */, - 9DE324B322D39A3700EB162A /* ParametersSerializer.swift in Sources */, 9DE324AF22D39A2D00EB162A /* PasscodeManger.swift in Sources */, 9581A9D92477D63C0066EF5E /* WKWebViewController.swift in Sources */, 9DF7964A2497C2D2001290DA /* AuthorizationContentView.swift in Sources */, diff --git a/Example/Authenticator/Utils/Helpers/ParametersSerializer.swift b/Example/Authenticator/Utils/Helpers/ParametersSerializer.swift deleted file mode 100644 index 81adad73..00000000 --- a/Example/Authenticator/Utils/Helpers/ParametersSerializer.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// ParametersSerializer.swift -// This file is part of the Salt Edge Authenticator distribution -// (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 3 or later. -// -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -// For the additional permissions granted for Salt Edge Authenticator -// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md -// - -import Foundation - -struct ParametersSerializer { - static func createBody(parameters: [String: Any]) -> Data? { - do { - let data = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted) - - return data - } catch { - Log.debugLog(message: error.localizedDescription) - } - - return nil - } -} diff --git a/Example/Tests/Spec Helpers/URLRequestBuilder.swift b/Example/Tests/Spec Helpers/URLRequestBuilder.swift index 1dacee0b..89a6a83c 100644 --- a/Example/Tests/Spec Helpers/URLRequestBuilder.swift +++ b/Example/Tests/Spec Helpers/URLRequestBuilder.swift @@ -22,7 +22,6 @@ import Foundation import SEAuthenticatorCore -@testable import SEAuthenticator struct URLRequestBuilder { static func buildUrlRequest( diff --git a/Example/Tests/v2/Networking/Routers/SEConnectionRouterSpecV2.swift b/Example/Tests/v2/Networking/Routers/SEConnectionRouterSpecV2.swift index b71dcd70..e6a7a629 100644 --- a/Example/Tests/v2/Networking/Routers/SEConnectionRouterSpecV2.swift +++ b/Example/Tests/v2/Networking/Routers/SEConnectionRouterSpecV2.swift @@ -61,10 +61,10 @@ class SEConnectionRouterSpecV2: BaseSpec { let connectionGuid = "guid" let entityId = "1" let accessToken = "access_token" - let parameters = [ + let parameters: [String : Any] = [ ParametersKeys.data: [:], ParametersKeys.exp: Date().addingTimeInterval(5.0 * 60.0).utcSeconds - ] as [String : Any] + ] let headers = Headers.signedRequestHeaders( token: accessToken, @@ -76,8 +76,7 @@ class SEConnectionRouterSpecV2: BaseSpec { with: baseUrl.appendingPathComponent("/api/authenticator/v2/connections/\(entityId)/revoke"), method: HTTPMethod.delete.rawValue, headers: headers, - params: parameters, - encoding: .url + params: parameters ) let actualRequest = SEConnectionRouter.revoke( diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConnectionRouter.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConnectionRouter.swift index 6d17a6d2..49926285 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConnectionRouter.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConnectionRouter.swift @@ -44,8 +44,7 @@ enum SEConnectionRouter: Routable { var encoding: Encoding { switch self { - case .createConnection: return .json - case .revoke: return .url + case .createConnection, .revoke: return .json } } From 552709d493dd7c2678114f5170de25b517e36649 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Mon, 24 May 2021 14:16:42 +0300 Subject: [PATCH 013/110] added authorization manager v2 and responses --- .../Classes/API/SENetKeys.swift | 1 + .../API/Managers/SEAuthorizationManager.swift | 80 +++++++++++++++++++ .../Models/SEAuthorizationRequestData.swift | 42 ++++++++++ .../Responses/AuthorizationResponses.swift | 66 +++++++++++++++ .../API/Routers/SEAuthorizationRouter.swift | 26 +----- .../API/SEEncryptedAuthorizationData.swift | 52 ++++++++++++ 6 files changed, 245 insertions(+), 22 deletions(-) create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEAuthorizationManager.swift create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEAuthorizationRequestData.swift create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Responses/AuthorizationResponses.swift create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/SEEncryptedAuthorizationData.swift diff --git a/SaltedgeAuthenticatorCore/Classes/API/SENetKeys.swift b/SaltedgeAuthenticatorCore/Classes/API/SENetKeys.swift index 7d62b5b4..5caf658a 100644 --- a/SaltedgeAuthenticatorCore/Classes/API/SENetKeys.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/SENetKeys.swift @@ -43,6 +43,7 @@ public struct SENetKeys { public static let message = "message" public static let success = "success" + public static let status = "status" public static let accessToken = "access_token" diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEAuthorizationManager.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEAuthorizationManager.swift new file mode 100644 index 00000000..e9dbb296 --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEAuthorizationManager.swift @@ -0,0 +1,80 @@ +// +// SEAuthorizationManager +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +public struct SEAuthorizationManager { + public static func getEncryptedAuthorizations( + url: URL, + accessToken: AccessToken, + onSuccess success: @escaping HTTPServiceSuccessClosure, + onFailure failure: @escaping FailureBlock + ) { + HTTPService.execute( + request: SEAuthorizationRouter.list(url, accessToken), + success: success, + failure: failure + ) + } + + public static func getEncryptedAuthorization( + data: SEBaseAuthenticatedWithIdRequestData, + onSuccess success: @escaping HTTPServiceSuccessClosure, + onFailure failure: @escaping FailureBlock + ) { + HTTPService.execute( + request: SEAuthorizationRouter.show(data), + success: success, + failure: failure + ) + } + + public static func confirmAuthorization( + url: URL, + accessToken: AccessToken, + data: SEAuthorizationRequestData, + onSuccess success: @escaping HTTPServiceSuccessClosure, + onFailure failure: @escaping FailureBlock + ) { + HTTPService.execute( + request: SEAuthorizationRouter.confirm(url, accessToken, data), + success: success, + failure: failure + ) + } + + public static func denyAuthorization( + url: URL, + accessToken: AccessToken, + data: SEAuthorizationRequestData, + onSuccess success: @escaping HTTPServiceSuccessClosure, + onFailure failure: @escaping FailureBlock + ) { + HTTPService.execute( + request: SEAuthorizationRouter.deny(url, accessToken, data), + success: success, + failure: failure + ) + } +} + diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEAuthorizationRequestData.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEAuthorizationRequestData.swift new file mode 100644 index 00000000..f20b97df --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEAuthorizationRequestData.swift @@ -0,0 +1,42 @@ +// +// SEAuthorizationRequestData +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +public struct SEAuthorizationRequestData { + public let id: ID + public let authorizationCode: String + public let userAuthorizationType: String + public let geolocation: String + public let connectionGuid: GUID + + public var encryptedData: SEEncryptedData? { + guard let data = [ + SENetKeys.authorizationCode: authorizationCode, + ApiConstants.userAuthorizationType: userAuthorizationType, + ApiConstants.geolocation: geolocation + ].jsonString else { return nil } + + return try? SECryptoHelper.encrypt(data, tag: SETagHelper.create(for: connectionGuid)) + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/AuthorizationResponses.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/AuthorizationResponses.swift new file mode 100644 index 00000000..f9d4e45e --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/AuthorizationResponses.swift @@ -0,0 +1,66 @@ +// +// AuthorizationResponses +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +public struct SEEncryptedAuthorizationDataResponse: SerializableResponse { + public var data: SEEncryptedAuthorizationData + + public init?(_ value: Any) { + if let response = (value as AnyObject)[SENetKeys.data] as? [String: Any], + let data = SEEncryptedAuthorizationData(response) { + self.data = data + } else { + return nil + } + } +} + +public struct SEEncryptedAuthorizationsListResponse: SerializableResponse { + public var data: [SEEncryptedAuthorizationData] = [] + + public init?(_ value: Any) { + if let responses = (value as AnyObject)[SENetKeys.data] as? [[String: Any]] { + self.data = responses.compactMap { SEEncryptedAuthorizationData($0) } + } else { + return nil + } + } +} + +public struct SEConfirmAuthorizationResponse: SerializableResponse { + public let id: String + public let status: String + + public init?(_ value: Any) { + if let dict = value as? [String: Any], + let data = dict[SENetKeys.data] as? [String: Any], + let status = data[SENetKeys.status] as? String, + let id = data[SENetKeys.id] as? String { + self.id = id + self.status = status + } else { + return nil + } + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEAuthorizationRouter.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEAuthorizationRouter.swift index fbdafdc5..59acdedf 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEAuthorizationRouter.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEAuthorizationRouter.swift @@ -22,25 +22,6 @@ import Foundation import SEAuthenticatorCore -import CryptoSwift - -public struct SEAuthorizationRequestData { - public let id: ID - public let authorizationCode: String - public let userAuthorizationType: String - public let geolocation: String - public let connectionGuid: GUID - - public var encryptedData: SEEncryptedData? { - guard let data = [ - SENetKeys.authorizationCode: authorizationCode, - ApiConstants.userAuthorizationType: userAuthorizationType, - ApiConstants.geolocation: geolocation - ].jsonString else { return nil } - - return try? SECryptoHelper.encrypt(data, tag: SETagHelper.create(for: connectionGuid)) - } -} enum SEAuthorizationRouter: Routable { case list(URL, AccessToken) @@ -64,9 +45,10 @@ enum SEAuthorizationRouter: Routable { var url: URL { switch self { - case .list(let url, _): return url.appendingPathComponent( - "\(SENetPathBuilder(for: .authorizations, version: 2).path)" - ) + case .list(let url, _): + return url.appendingPathComponent( + "\(SENetPathBuilder(for: .authorizations, version: 2).path)" + ) case .show(let data): return data.url.appendingPathComponent( "\(SENetPathBuilder(for: .authorizations, version: 2).path)/\(data.entityId)" diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/SEEncryptedAuthorizationData.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/SEEncryptedAuthorizationData.swift new file mode 100644 index 00000000..dfd6d8d5 --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/SEEncryptedAuthorizationData.swift @@ -0,0 +1,52 @@ +// +// SEEncryptedAuthorizationData +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +public struct SEEncryptedAuthorizationData: SerializableResponse { + public let id: String + public let data: String + public let key: String + public let iv: String + public let status: String + public var connectionId: String + + public init?(_ value: Any) { + if let dict = value as? [String: Any], + let id = dict[SENetKeys.id] as? String, + let data = dict[SENetKeys.data] as? String, + let key = dict[SENetKeys.key] as? String, + let iv = dict[SENetKeys.iv] as? String, + let status = dict[SENetKeys.status] as? String, + let connectionId = dict[SENetKeys.connectionId] as? String { + self.id = id + self.data = data + self.key = key + self.iv = iv + self.status = status + self.connectionId = connectionId + } else { + return nil + } + } +} From 6ef3778f1acd9a8d05de70e7d20b92006cac353e Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Wed, 26 May 2021 16:06:35 +0300 Subject: [PATCH 014/110] added connections interactor --- .../Authenticator.xcodeproj/project.pbxproj | 46 +++++- .../Connect/QRCodeCoordinator.swift | 4 +- .../Main/ApplicationCoordinator.swift | 4 +- .../Models/Database/Connection.swift | 4 + .../Utils/Handlers/ConnectHandler.swift | 101 +++++++++---- .../{ => v1}/AuthorizationsInteractor.swift | 0 .../{ => v1}/CollectionsInteractor.swift | 0 .../{ => v1}/ConnectionsInteractor.swift | 4 +- .../{ => v1}/ConsentsInteractor.swift | 0 .../v2/ConnectionsInteractorV2.swift | 135 ++++++++++++++++++ Example/Tests/Crypto/SECryptoHelperSpec.swift | 45 ++++++ .../Helpers/ApiVersionExtractorSpec.swift | 47 ++++++ .../Responses/ProviderResponseSpec.swift | 2 +- Example/Tests/Spec Helpers/SpecUtils.swift | 12 ++ .../Routers/SEConnectionRouterSpecV2.swift | 3 +- .../Classes/API/SEConnectHelper.swift | 6 +- .../Classes/Crypto/SECryptoHelper.swift | 55 ++++++- .../Classes/Helpers/ApiVersionExtractor.swift | 35 +++++ .../Classes/Helpers/TypeAliases.swift | 2 + .../Models/Responses/SEProviderResponse.swift | 4 +- ...anager.swift => SEProviderManagerV2.swift} | 10 +- ...ponse.swift => SEProviderResponseV2.swift} | 19 +-- .../API/Routers/SEConnectionRouter.swift | 13 +- ...rRouter.swift => SEProviderRouterV2.swift} | 6 +- .../Classes/RequestParametersBuilder.swift | 25 ++-- 25 files changed, 500 insertions(+), 82 deletions(-) rename Example/Authenticator/Utils/Interactors/{ => v1}/AuthorizationsInteractor.swift (100%) rename Example/Authenticator/Utils/Interactors/{ => v1}/CollectionsInteractor.swift (100%) rename Example/Authenticator/Utils/Interactors/{ => v1}/ConnectionsInteractor.swift (97%) rename Example/Authenticator/Utils/Interactors/{ => v1}/ConsentsInteractor.swift (100%) create mode 100644 Example/Authenticator/Utils/Interactors/v2/ConnectionsInteractorV2.swift create mode 100644 Example/Tests/Crypto/SECryptoHelperSpec.swift create mode 100644 Example/Tests/Helpers/ApiVersionExtractorSpec.swift rename {SaltedgeAuthenticatorSDK => SaltedgeAuthenticatorCore}/Classes/API/SEConnectHelper.swift (95%) create mode 100644 SaltedgeAuthenticatorCore/Classes/Helpers/ApiVersionExtractor.swift rename SaltedgeAuthenticatorSDKv2/Classes/API/Managers/{SEProviderManager.swift => SEProviderManagerV2.swift} (86%) rename SaltedgeAuthenticatorSDKv2/Classes/API/Responses/{SEProviderResponse.swift => SEProviderResponseV2.swift} (82%) rename SaltedgeAuthenticatorSDKv2/Classes/API/Routers/{SEProviderRouter.swift => SEProviderRouterV2.swift} (92%) diff --git a/Example/Authenticator.xcodeproj/project.pbxproj b/Example/Authenticator.xcodeproj/project.pbxproj index ab46d99b..7405e103 100644 --- a/Example/Authenticator.xcodeproj/project.pbxproj +++ b/Example/Authenticator.xcodeproj/project.pbxproj @@ -154,6 +154,9 @@ 9DBAD7A3247E65D30023F944 /* InstantActionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DBAD7A1247E65D30023F944 /* InstantActionCoordinator.swift */; }; 9DCBB28B243618B900DB3F85 /* Observable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCBB28A243618B900DB3F85 /* Observable.swift */; }; 9DCBB28C243618B900DB3F85 /* Observable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCBB28A243618B900DB3F85 /* Observable.swift */; }; + 9DCC5E25265D1DD40054B933 /* ConnectionsInteractorV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCC5E24265D1DD40054B933 /* ConnectionsInteractorV2.swift */; }; + 9DCC5E26265D1DD40054B933 /* ConnectionsInteractorV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCC5E24265D1DD40054B933 /* ConnectionsInteractorV2.swift */; }; + 9DCC5E29265D2F150054B933 /* SECryptoHelperSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCC5E28265D2F150054B933 /* SECryptoHelperSpec.swift */; }; 9DCED89A22CE18280050ED3C /* APIErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED7DD22CE18270050ED3C /* APIErrors.swift */; }; 9DCED89C22CE18280050ED3C /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED7E122CE18270050ED3C /* SettingsViewController.swift */; }; 9DCED89E22CE18280050ED3C /* ConnectionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED7E422CE18270050ED3C /* ConnectionsViewController.swift */; }; @@ -225,6 +228,7 @@ 9DCED92A22CE18280050ED3C /* Authenticator.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9DCED89522CE18280050ED3C /* Authenticator.strings */; }; 9DCED92D22CE1DC30050ED3C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9DCED92B22CE1DC30050ED3C /* LaunchScreen.storyboard */; }; 9DCED93722D3389C0050ED3C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9DCED93622D3389C0050ED3C /* Assets.xcassets */; }; + 9DD155EC265E74C800AEFA0D /* ApiVersionExtractorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD155EB265E74C800AEFA0D /* ApiVersionExtractorSpec.swift */; }; 9DD4DF252375969A000A9B80 /* QuickActionsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD4DF242375969A000A9B80 /* QuickActionsHelper.swift */; }; 9DD4DF262375969B000A9B80 /* QuickActionsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD4DF242375969A000A9B80 /* QuickActionsHelper.swift */; }; 9DD4DF2823759767000A9B80 /* AVCaptureHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD4DF2723759767000A9B80 /* AVCaptureHelper.swift */; }; @@ -465,6 +469,8 @@ 9DBAD79E247E55BD0023F944 /* QRCodeCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeCoordinator.swift; sourceTree = ""; }; 9DBAD7A1247E65D30023F944 /* InstantActionCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantActionCoordinator.swift; sourceTree = ""; }; 9DCBB28A243618B900DB3F85 /* Observable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Observable.swift; sourceTree = ""; }; + 9DCC5E24265D1DD40054B933 /* ConnectionsInteractorV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionsInteractorV2.swift; sourceTree = ""; }; + 9DCC5E28265D2F150054B933 /* SECryptoHelperSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SECryptoHelperSpec.swift; sourceTree = ""; }; 9DCED7DD22CE18270050ED3C /* APIErrors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIErrors.swift; sourceTree = ""; }; 9DCED7E022CE18270050ED3C /* LanguagePickerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LanguagePickerViewController.swift; sourceTree = ""; }; 9DCED7E122CE18270050ED3C /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; @@ -544,6 +550,7 @@ 9DCED89722CE18280050ED3C /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Authenticator.strings; sourceTree = ""; }; 9DCED92C22CE1DC30050ED3C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 9DCED93622D3389C0050ED3C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Authenticator/Assets.xcassets; sourceTree = SOURCE_ROOT; }; + 9DD155EB265E74C800AEFA0D /* ApiVersionExtractorSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiVersionExtractorSpec.swift; sourceTree = ""; }; 9DD4DF242375969A000A9B80 /* QuickActionsHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickActionsHelper.swift; sourceTree = ""; }; 9DD4DF2723759767000A9B80 /* AVCaptureHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVCaptureHelper.swift; sourceTree = ""; }; 9DD50990247527D800CB188D /* AuthorizationsViewModelSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorizationsViewModelSpec.swift; sourceTree = ""; }; @@ -680,6 +687,7 @@ 607FACE81AFB9204008FA782 /* Tests */ = { isa = PBXGroup; children = ( + 9DCC5E27265D2F050054B933 /* Crypto */, 9D69071A2655437E00A9CE94 /* v2 */, 9DCBB2872435F1EF00DB3F85 /* ViewModels */, 9D66E1D222D6146800BD59B6 /* Networking */, @@ -795,6 +803,7 @@ 9D6575FA232A9E8100DCC53E /* DateUtilsSpec.swift */, 95CD02DC235864130085C52A /* SEConnectHelperSpec.swift */, 0479C5512387F8C300F3FE0A /* AVCaptureHelperSpec.swift */, + 9DD155EB265E74C800AEFA0D /* ApiVersionExtractorSpec.swift */, ); path = Helpers; sourceTree = ""; @@ -962,6 +971,33 @@ path = Authorizations; sourceTree = ""; }; + 9DCC5E22265D1DB70054B933 /* v2 */ = { + isa = PBXGroup; + children = ( + 9DCC5E24265D1DD40054B933 /* ConnectionsInteractorV2.swift */, + ); + path = v2; + sourceTree = ""; + }; + 9DCC5E23265D1DBD0054B933 /* v1 */ = { + isa = PBXGroup; + children = ( + 9DCED82C22CE18270050ED3C /* AuthorizationsInteractor.swift */, + 9DCED82D22CE18270050ED3C /* ConnectionsInteractor.swift */, + 9D98747B24B390F200EE89BB /* ConsentsInteractor.swift */, + 95C6781E24A9FD8B0088A9CF /* CollectionsInteractor.swift */, + ); + path = v1; + sourceTree = ""; + }; + 9DCC5E27265D2F050054B933 /* Crypto */ = { + isa = PBXGroup; + children = ( + 9DCC5E28265D2F150054B933 /* SECryptoHelperSpec.swift */, + ); + path = Crypto; + sourceTree = ""; + }; 9DCED7DC22CE18270050ED3C /* Errors */ = { isa = PBXGroup; children = ( @@ -1084,10 +1120,8 @@ 9DCED82B22CE18270050ED3C /* Interactors */ = { isa = PBXGroup; children = ( - 9DCED82C22CE18270050ED3C /* AuthorizationsInteractor.swift */, - 9DCED82D22CE18270050ED3C /* ConnectionsInteractor.swift */, - 9D98747B24B390F200EE89BB /* ConsentsInteractor.swift */, - 95C6781E24A9FD8B0088A9CF /* CollectionsInteractor.swift */, + 9DCC5E23265D1DBD0054B933 /* v1 */, + 9DCC5E22265D1DB70054B933 /* v2 */, ); path = Interactors; sourceTree = ""; @@ -1867,6 +1901,7 @@ 9DE56A0C24AB702A00FA5B2C /* ConsentsCoordinator.swift in Sources */, 9DCED90322CE18280050ED3C /* TaptileFeedbackButton.swift in Sources */, 9DCED8E022CE18280050ED3C /* UserDefaultsHelper.swift in Sources */, + 9DCC5E25265D1DD40054B933 /* ConnectionsInteractorV2.swift in Sources */, 9DCED8D522CE18280050ED3C /* ApplicationExtensions.swift in Sources */, 9DCED8D622CE18280050ED3C /* ViewExtensions+Animations.swift in Sources */, 9DCED89C22CE18280050ED3C /* SettingsViewController.swift in Sources */, @@ -1953,6 +1988,7 @@ 9D66E1E522D6146900BD59B6 /* HeadersSpec.swift in Sources */, 95F409032475620600727095 /* ConnectionsViewModel.swift in Sources */, 9DE3247122D398ED00EB162A /* SpecUtils.swift in Sources */, + 9DCC5E26265D1DD40054B933 /* ConnectionsInteractorV2.swift in Sources */, 9DE3248A22D399B800EB162A /* AuthorizationsDataSource.swift in Sources */, 9DE3247222D398ED00EB162A /* SpecCryptoHelper.swift in Sources */, 9DE324B622D39A4700EB162A /* SettingsViewController.swift in Sources */, @@ -2029,6 +2065,7 @@ 9D66E1B622D4C1F700BD59B6 /* ConnectionRepositorySpec.swift in Sources */, 9DE56A0324AA179100FA5B2C /* ConsentLogoView.swift in Sources */, 959E30A1247BC78D0067354A /* LanguagePickerViewModel.swift in Sources */, + 9DCC5E29265D2F150054B933 /* SECryptoHelperSpec.swift in Sources */, 9DE3247E22D3998D00EB162A /* PasscodeCoordinator.swift in Sources */, 9D6575F8232A9B0200DCC53E /* DateExtensionsSpec.swift in Sources */, 9DE3249E22D399F900EB162A /* DicitionaryExtensions.swift in Sources */, @@ -2115,6 +2152,7 @@ 959E309F247BC7880067354A /* LanguagePickerViewController.swift in Sources */, 9DE3248D22D399C200EB162A /* ConnectionRepository.swift in Sources */, 9D66E1AF22D4C1D700BD59B6 /* ConnectionDataSpec.swift in Sources */, + 9DD155EC265E74C800AEFA0D /* ApiVersionExtractorSpec.swift in Sources */, 9DE324E122D39AD000EB162A /* AppDelegate.swift in Sources */, 9D1DEE8123DB5177003C79AC /* ActionRouterSpec.swift in Sources */, ); diff --git a/Example/Authenticator/Coordinators/Connect/QRCodeCoordinator.swift b/Example/Authenticator/Coordinators/Connect/QRCodeCoordinator.swift index 6d5cc723..d0a242e8 100644 --- a/Example/Authenticator/Coordinators/Connect/QRCodeCoordinator.swift +++ b/Example/Authenticator/Coordinators/Connect/QRCodeCoordinator.swift @@ -21,7 +21,7 @@ // import UIKit -import SEAuthenticator +import SEAuthenticatorCore final class QRCodeCoordinator: Coordinator { private var rootViewController: UIViewController @@ -55,6 +55,8 @@ extension QRCodeCoordinator: QRCodeViewControllerDelegate { guard let url = URL(string: data), SEConnectHelper.isValid(deepLinkUrl: url) else { return } + let apiVersion = data.apiVerion + if let actionGuid = SEConnectHelper.actionGuid(from: url), let connectUrl = SEConnectHelper.connectUrl(from: url) { instantActionCoordinator = InstantActionCoordinator( diff --git a/Example/Authenticator/Coordinators/Main/ApplicationCoordinator.swift b/Example/Authenticator/Coordinators/Main/ApplicationCoordinator.swift index 866dc8dc..3c75240c 100644 --- a/Example/Authenticator/Coordinators/Main/ApplicationCoordinator.swift +++ b/Example/Authenticator/Coordinators/Main/ApplicationCoordinator.swift @@ -21,7 +21,7 @@ // import UIKit -import SEAuthenticator +import SEAuthenticatorCore final class ApplicationCoordinator: Coordinator { private let window: UIWindow? @@ -236,6 +236,8 @@ final class ApplicationCoordinator: Coordinator { } private func startConnect(url: URL, controller: UIViewController) { + let apiVersion = url.absoluteString.apiVerion + if let actionGuid = SEConnectHelper.actionGuid(from: url), let connectUrl = SEConnectHelper.connectUrl(from: url) { instantActionCoordinator = InstantActionCoordinator( diff --git a/Example/Authenticator/Models/Database/Connection.swift b/Example/Authenticator/Models/Database/Connection.swift index 1fe875bb..22683eb7 100644 --- a/Example/Authenticator/Models/Database/Connection.swift +++ b/Example/Authenticator/Models/Database/Connection.swift @@ -42,6 +42,10 @@ enum ConnectionStatus: String { dynamic var createdAt: Date = Date() dynamic var updatedAt: Date = Date() + dynamic var providerId: Int? + dynamic var publicKey: String = "" + dynamic var apiVersion: String = "1" + override static func primaryKey() -> String? { return #keyPath(Connection.guid) } diff --git a/Example/Authenticator/Utils/Handlers/ConnectHandler.swift b/Example/Authenticator/Utils/Handlers/ConnectHandler.swift index e6d2424b..c8e2062e 100644 --- a/Example/Authenticator/Utils/Handlers/ConnectHandler.swift +++ b/Example/Authenticator/Utils/Handlers/ConnectHandler.swift @@ -21,7 +21,6 @@ // import Foundation -import SEAuthenticator import SEAuthenticatorCore protocol ConnectEventsDelegate: class { @@ -87,41 +86,81 @@ final class ConnectHandler { } private func createNewConnection(from configurationUrl: URL, with connectQuery: String?) { - ConnectionsInteractor.createNewConnection( - from: configurationUrl, - with: connectQuery, - success: { [weak self] connection, accessToken in - self?.connection = connection - self?.saveConnectionAndFinish(with: accessToken) - }, - redirect: { [weak self] connection, connectUrl in - self?.connection = connection - self?.delegate?.startWebViewLoading(with: connectUrl) - }, - failure: { [weak self] error in - self?.dismissConnectWithError(error: error) - } - ) + let apiVersion = configurationUrl.absoluteString.apiVerion + + if apiVersion == "1" { + ConnectionsInteractor.createNewConnection( + from: configurationUrl, + with: connectQuery, + success: { [weak self] connection, accessToken in + self?.connection = connection + self?.saveConnectionAndFinish(with: accessToken) + }, + redirect: { [weak self] connection, connectUrl in + self?.connection = connection + self?.delegate?.startWebViewLoading(with: connectUrl) + }, + failure: { [weak self] error in + self?.dismissConnectWithError(error: error) + } + ) + } else { + ConnectionsInteractorV2.createNewConnection( + from: configurationUrl, + with: connectQuery, + success: { [weak self] connection, accessToken in + self?.connection = connection + self?.saveConnectionAndFinish(with: accessToken) + }, + redirect: { [weak self] connection, connectUrl in + self?.connection = connection + self?.delegate?.startWebViewLoading(with: connectUrl) + }, + failure: { [weak self] error in + self?.dismissConnectWithError(error: error) + } + ) + } } private func reconnectConnection(_ connectionId: String) { guard let connection = ConnectionsCollector.with(id: connectionId) else { return } - ConnectionsInteractor.submitNewConnection( - for: connection, - connectQuery: nil, - success: { [weak self] connection, accessToken in - self?.connection = connection - self?.saveConnectionAndFinish(with: accessToken) - }, - redirect: { [weak self] connection, connectUrl in - self?.connection = connection - self?.delegate?.startWebViewLoading(with: connectUrl) - }, - failure: { [weak self] error in - self?.dismissConnectWithError(error: error) - } - ) + let apiVersion = connection.baseUrlString.apiVerion + + if apiVersion == "1" { + ConnectionsInteractor.submitNewConnection( + for: connection, + connectQuery: nil, + success: { [weak self] connection, accessToken in + self?.connection = connection + self?.saveConnectionAndFinish(with: accessToken) + }, + redirect: { [weak self] connection, connectUrl in + self?.connection = connection + self?.delegate?.startWebViewLoading(with: connectUrl) + }, + failure: { [weak self] error in + self?.dismissConnectWithError(error: error) + } + ) + } else { + ConnectionsInteractorV2.submitNewConnection( + for: connection, + connectQuery: nil, + success: { [weak self] connection, accessToken in + self?.connection = connection + self?.saveConnectionAndFinish(with: accessToken) + }, + redirect: { [weak self] connection, connectUrl in + self?.connection = connection + self?.delegate?.startWebViewLoading(with: connectUrl) + }, + failure: { [weak self] error in + self?.dismissConnectWithError(error: error) + } + ) + } } private func dismissConnectWithError(error: String) { diff --git a/Example/Authenticator/Utils/Interactors/AuthorizationsInteractor.swift b/Example/Authenticator/Utils/Interactors/v1/AuthorizationsInteractor.swift similarity index 100% rename from Example/Authenticator/Utils/Interactors/AuthorizationsInteractor.swift rename to Example/Authenticator/Utils/Interactors/v1/AuthorizationsInteractor.swift diff --git a/Example/Authenticator/Utils/Interactors/CollectionsInteractor.swift b/Example/Authenticator/Utils/Interactors/v1/CollectionsInteractor.swift similarity index 100% rename from Example/Authenticator/Utils/Interactors/CollectionsInteractor.swift rename to Example/Authenticator/Utils/Interactors/v1/CollectionsInteractor.swift diff --git a/Example/Authenticator/Utils/Interactors/ConnectionsInteractor.swift b/Example/Authenticator/Utils/Interactors/v1/ConnectionsInteractor.swift similarity index 97% rename from Example/Authenticator/Utils/Interactors/ConnectionsInteractor.swift rename to Example/Authenticator/Utils/Interactors/v1/ConnectionsInteractor.swift index 28da8153..e3cfa878 100644 --- a/Example/Authenticator/Utils/Interactors/ConnectionsInteractor.swift +++ b/Example/Authenticator/Utils/Interactors/v1/ConnectionsInteractor.swift @@ -46,7 +46,7 @@ struct ConnectionsInteractor { connection.supportEmail = response.supportEmail connection.logoUrlString = response.logoUrl?.absoluteString ?? "" - connection.baseUrlString = response.connectUrl.absoluteString + connection.baseUrlString = response.baseUrl.absoluteString connection.geolocationRequired.value = response.geolocationRequired submitNewConnection( @@ -117,7 +117,7 @@ struct ConnectionsInteractor { entityId: connection.id ) - SEConnectionManager.revokeConnection( + SEAuthenticator.SEConnectionManager.revokeConnection( data: data, onSuccess: { _ in success?() diff --git a/Example/Authenticator/Utils/Interactors/ConsentsInteractor.swift b/Example/Authenticator/Utils/Interactors/v1/ConsentsInteractor.swift similarity index 100% rename from Example/Authenticator/Utils/Interactors/ConsentsInteractor.swift rename to Example/Authenticator/Utils/Interactors/v1/ConsentsInteractor.swift diff --git a/Example/Authenticator/Utils/Interactors/v2/ConnectionsInteractorV2.swift b/Example/Authenticator/Utils/Interactors/v2/ConnectionsInteractorV2.swift new file mode 100644 index 00000000..d25e2223 --- /dev/null +++ b/Example/Authenticator/Utils/Interactors/v2/ConnectionsInteractorV2.swift @@ -0,0 +1,135 @@ +// +// ConnectionsInteractorV2 +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorV2 +import SEAuthenticatorCore + +struct ConnectionsInteractorV2 { + /* + Request to create new SCA Service connection. + Result is returned through callback. + */ + static func createNewConnection( + from url: URL, + with connectQuery: String?, + success: @escaping (Connection, AccessToken) -> (), + redirect: @escaping (Connection, String) -> (), + failure: @escaping (String) -> () + ) { + getProviderConfiguration( + from: url, + success: { response in + let connection = Connection() + + if ConnectionsCollector.connectionNames.contains(response.name) { + connection.name = "\(response.name) (\(ConnectionsCollector.connectionNames.count + 1))" + } else { + connection.name = response.name + } + + connection.providerId = response.providerId + connection.publicKey = response.publicKey + connection.apiVersion = response.apiVersion + connection.supportEmail = response.supportEmail + connection.logoUrlString = response.logoUrl?.absoluteString ?? "" + connection.baseUrlString = response.baseUrl.absoluteString + connection.geolocationRequired.value = response.geolocationRequired + + submitNewConnection( + for: connection, + connectQuery: connectQuery, + success: success, + redirect: redirect, + failure: failure + ) + }, + failure: failure + ) + } + + /* + Request to get SCA Service connection. + Result is returned through callback. + */ + static func getProviderConfiguration( + from url: URL, + success: @escaping (SEProviderResponseV2) -> (), + failure: @escaping (String) -> () + ) { + SEProviderManagerV2.fetchProviderData( + url: url, + onSuccess: success, + onFailure: failure + ) + } + + static func submitNewConnection( + for connection: Connection, + connectQuery: String?, + success: @escaping (Connection, AccessToken) -> (), + redirect: @escaping (Connection, String) -> (), + failure: @escaping (String) -> () + ) { + // 1. Create Provider's public key (SecKey) + SECryptoHelper.createKey( + from: connection.publicKey, + isPublic: true, + tag: "\(connection.guid)_provider_public_key" + ) + + // 2. Generate new RSA key pair for a new Connection by tag + SECryptoHelper.createKeyPair(with: SETagHelper.create(for: connection.guid)) + + // 3. Convert Connection's public key to pem + guard let connectionPublicKeyPem = SECryptoHelper.publicKeyToPem(tag: SETagHelper.create(for: connection.guid)), + // 4. Encrypt Connection's public key with Provider's public key (step 1) + let encryptedData = try? SECryptoHelper.encrypt( + connectionPublicKeyPem, + tag: "\(connection.guid)_provider_public_key" + ), + let providerId = connection.providerId else { return } + + let params = SECreateConnectionParams( + providerId: providerId, + pushToken: UserDefaultsHelper.pushToken, + connectQuery: connectQuery, + encryptedRsaPublicKey: encryptedData + ) + + guard let connectUrl = connection.baseUrl else { return } + + // 5. Send request + SEConnectionManager.createConnection( + by: connectUrl, + params: params, + appLanguage: "en", + onSuccess: { response in + // TODO: Finish + print(response) + }, + onFailure: { error in + print(error) + } + ) + } +} diff --git a/Example/Tests/Crypto/SECryptoHelperSpec.swift b/Example/Tests/Crypto/SECryptoHelperSpec.swift new file mode 100644 index 00000000..e80108b0 --- /dev/null +++ b/Example/Tests/Crypto/SECryptoHelperSpec.swift @@ -0,0 +1,45 @@ +// +// SECryptoHelperSpec +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Quick +import Nimble +@testable import SEAuthenticatorCore + +class SECryptoHelperSpec: BaseSpec { + override func spec() { + fdescribe("publicKeyToPem") { + it("should do") { +// print(SpecUtils.publicKeyPem.pemToPublicKey) +// let tag = "test" +// let key = try? SECryptoHelper.createKey(from: DataFixtures.publicKey, isPublic: true, tag: tag) + +// let expectedPem = try! SECryptoHelper.publicKeyData(for: tag).string +// expect(specu).to(equal(SpecUtils.publicKeyPem)) + } + +// let tag = SETagHelper.create(for: "test") +// _ = SECryptoHelper.createKeyPair(with: tag) +// +// print(SECryptoHelper.publicKeyToPem(tag: tag)) + } + } +} diff --git a/Example/Tests/Helpers/ApiVersionExtractorSpec.swift b/Example/Tests/Helpers/ApiVersionExtractorSpec.swift new file mode 100644 index 00000000..61b9c3e1 --- /dev/null +++ b/Example/Tests/Helpers/ApiVersionExtractorSpec.swift @@ -0,0 +1,47 @@ +// +// ApiVersionExtractorSpec +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Quick +import Nimble +@testable import SEAuthenticatorCore + +class ApiVersionExtractorSpec: BaseSpec { + override func spec() { + describe("apiVersion") { + context("when url contains version") { + it("should return correct version from given url") { + let urlString = "https://sca.banksalt.com/api/authenticator/v2/configurations/1" + + expect(urlString.apiVerion).to(equal("2")) + } + } + + context("when url doesn't contain version") { + it("should return default value 1") { + let urlString = "https://sca.banksalt.com/api/authenticator/configurations/1" + + expect(urlString.apiVerion).to(equal("1")) + } + } + } + } +} diff --git a/Example/Tests/Networking/Responses/ProviderResponseSpec.swift b/Example/Tests/Networking/Responses/ProviderResponseSpec.swift index 2b4607f7..a6531743 100644 --- a/Example/Tests/Networking/Responses/ProviderResponseSpec.swift +++ b/Example/Tests/Networking/Responses/ProviderResponseSpec.swift @@ -34,7 +34,7 @@ class ProviderResponseSpec: BaseSpec { expect(expectedResponse).toNot(beNil()) expect(expectedResponse?.code).to(equal("demobank")) - expect(expectedResponse?.connectUrl.absoluteString).to(equal("getConnectUrl.com")) + expect(expectedResponse?.baseUrl.absoluteString).to(equal("getConnectUrl.com")) expect(expectedResponse?.name).to(equal("Demobank")) expect(expectedResponse?.logoUrl?.absoluteString).to(equal("https://upload.wikimedia.org/wikipedia/commons/6/6f/HP_logo_630x630.png")) expect(expectedResponse?.version).to(equal("1")) diff --git a/Example/Tests/Spec Helpers/SpecUtils.swift b/Example/Tests/Spec Helpers/SpecUtils.swift index 8833025a..4e5320e5 100644 --- a/Example/Tests/Spec Helpers/SpecUtils.swift +++ b/Example/Tests/Spec Helpers/SpecUtils.swift @@ -57,4 +57,16 @@ struct SpecUtils { return SEEncryptedData(dict)!.decryptedAuthorizationData! } + + public static var publicKeyPem: String { + "-----BEGIN PUBLIC KEY-----\n" + + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAppVU/nZZVewUCRVLz51X\n" + + "iKcliziIOb5/ReqHH82ikgC517/7Qo/cBFK+/iOC+yDgULkJE3SMhG85JoCqeX7j\n" + + "YzeILe5LLgqAxLCOjQFnkQDaHwP2WShU8WQifZ58UY5Th2GCKScFrsLxPr8HLWJH\n" + + "cPC6qicuOmgvyT64SvWFh8l5nHWcx/RA7e5Z4eCRntqyVDv622/vYybNInFMvqB+\n" + + "oEGOhEyh/qCYmIumEH3QH91eqCd05/Z9PtugH08TqRPDL6s5GunfTsBHYhJdxDTc\n" + + "qh0etk+TnUqYON7jOXDAN7L8y5VI/UELVONBJy8MzcyER1pyPhrnCDMaKX6+LcpB\n" + + "owIDAQAB\n" + + "-----END PUBLIC KEY-----\n" + } } diff --git a/Example/Tests/v2/Networking/Routers/SEConnectionRouterSpecV2.swift b/Example/Tests/v2/Networking/Routers/SEConnectionRouterSpecV2.swift index e6a7a629..9efd8f5d 100644 --- a/Example/Tests/v2/Networking/Routers/SEConnectionRouterSpecV2.swift +++ b/Example/Tests/v2/Networking/Routers/SEConnectionRouterSpecV2.swift @@ -35,8 +35,7 @@ class SEConnectionRouterSpecV2: BaseSpec { it("should create a valid url request") { let encryptedData = SEEncryptedData(data: "data", key: "key", iv: "iv") let data = SECreateConnectionParams( - providerId: "12", - returnUrl: "return.com", + providerId: 12, pushToken: "push_token", connectQuery: "connect_query", encryptedRsaPublicKey: encryptedData diff --git a/SaltedgeAuthenticatorSDK/Classes/API/SEConnectHelper.swift b/SaltedgeAuthenticatorCore/Classes/API/SEConnectHelper.swift similarity index 95% rename from SaltedgeAuthenticatorSDK/Classes/API/SEConnectHelper.swift rename to SaltedgeAuthenticatorCore/Classes/API/SEConnectHelper.swift index 28502b3a..bb35a618 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/SEConnectHelper.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/SEConnectHelper.swift @@ -1,8 +1,8 @@ // -// SEConnectHelper.swift +// SEConnectHelper // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -21,7 +21,6 @@ // import Foundation -import SEAuthenticatorCore public struct SEConnectHelper { public static func isValid(deepLinkUrl url: URL) -> Bool { @@ -62,3 +61,4 @@ public struct SEConnectHelper { return query } } + diff --git a/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelper.swift b/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelper.swift index e4725fd2..c68bd411 100644 --- a/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelper.swift +++ b/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelper.swift @@ -28,6 +28,14 @@ public typealias KeyPair = (publicKey: SecKey, privateKey: SecKey) public struct SECryptoHelper { // MARK: - Public Methods + + /* + Create RSA key pair and store it in Keychain + + - parameters: + - tag: An unique identifier for a newly generated key pair, by which the key could be retrieved from Keychain + */ + @discardableResult public static func createKeyPair(with tag: KeyTag) -> KeyPair? { SecKeyHelper.deleteKey(tag) SecKeyHelper.deleteKey(tag.privateTag) @@ -35,11 +43,44 @@ public struct SECryptoHelper { return SecKeyHelper.generateKeyPair(tag: tag) } - public static func createKey(fromFile name: String, isPublic: Bool, tag: String) throws -> SecKey { + /* + Convert private key from asymmetric key pair to pem string + + - parameters: + - tag: An unique identifier by which was created the private key + */ + public static func privateKeyToPem(tag: KeyTag) -> String? { + guard let encodedKey = try? privateKeyData(for: tag.privateTag).base64EncodedString() else { return nil } + + return "-----BEGIN PRIVATE KEY-----\n\(encodedKey)\n-----END PRIVATE KEY-----\n" + } + + /* + Convert public key from asymmetric key pair to pem string + + - parameters: + - tag: An unique identifier by which was created the public key + */ + public static func publicKeyToPem(tag: KeyTag) -> String? { + guard let encodedKey = try? privateKeyData(for: tag).base64EncodedString() else { return nil } + + return "-----BEGIN PUBLIC KEY-----\n\(encodedKey)\n-----END PUBLIC KEY-----\n" + } + + /* + Converts string which contains private key in PEM format to SecKey object + + - parameters: + - pem: Key in pem format + - isPublic: Type of the key + - tag: An unique identifier by which the key could be retrieved from Keychain + */ + @discardableResult + public static func createKey(from pem: String, isPublic: Bool, tag: String) -> SecKey? { SecKeyHelper.deleteKey(tag) SecKeyHelper.deleteKey(tag.privateTag) - return try SecKeyHelper.createKey(fromFile: name, isPublic: isPublic, tag: tag) + return try? SecKeyHelper.createKey(from: pem, isPublic: isPublic, tag: tag) } @discardableResult @@ -80,6 +121,10 @@ public struct SECryptoHelper { return try SecKeyHelper.obtainKeyData(for: tag) } + public static func privateKeyData(for tag: KeyTag) throws -> Data { + return try SecKeyHelper.obtainKeyData(for: tag.privateTag) + } + public static func publicKey(for tag: KeyTag) throws -> SecKey { return try SecKeyHelper.obtainKey(for: tag) } @@ -244,8 +289,8 @@ private struct SecKeyHelper { return nil } - static func createKey(fromFile name: String, isPublic: Bool, tag: String) throws -> SecKey { - let base64Encoded = base64String(pemEncoded: pem(name)) + static func createKey(from pem: String, isPublic: Bool, tag: String) throws -> SecKey { + let base64Encoded = base64String(pemEncoded: pem) guard let data = Data(base64Encoded: base64Encoded, options: [.ignoreUnknownCharacters]) else { throw SECryptoHelperError.errorCreatingData(fromBase64: base64Encoded) @@ -326,7 +371,7 @@ private struct SecKeyHelper { static func base64String(pemEncoded pemString: String) -> String { return pemString.components(separatedBy: "\n").filter { line in return !line.hasPrefix("-----BEGIN") && !line.hasPrefix("-----END") - }.joined(separator: "") + }.joined(separator: "") } static func pem(_ filename: String) -> String { diff --git a/SaltedgeAuthenticatorCore/Classes/Helpers/ApiVersionExtractor.swift b/SaltedgeAuthenticatorCore/Classes/Helpers/ApiVersionExtractor.swift new file mode 100644 index 00000000..28a0e8d3 --- /dev/null +++ b/SaltedgeAuthenticatorCore/Classes/Helpers/ApiVersionExtractor.swift @@ -0,0 +1,35 @@ +// +// ConnectAppLinkData +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation + +public extension String { + var apiVerion: String { + let segments = split(separator: "/") + + guard let index = segments.firstIndex(of: "authenticator") else { return "1" } + + let apiVersionValue = segments[index + 1].replacingOccurrences(of: "v", with: "") + + return apiVersionValue.isEmpty ? "1" : apiVersionValue + } +} diff --git a/SaltedgeAuthenticatorCore/Classes/Helpers/TypeAliases.swift b/SaltedgeAuthenticatorCore/Classes/Helpers/TypeAliases.swift index 19e1f2ed..70ecc0a3 100644 --- a/SaltedgeAuthenticatorCore/Classes/Helpers/TypeAliases.swift +++ b/SaltedgeAuthenticatorCore/Classes/Helpers/TypeAliases.swift @@ -20,6 +20,8 @@ // under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md // +public typealias ApiVersion = String + public typealias SuccessBlock = () -> () public typealias FailureBlock = (String) -> () public typealias RequestSuccessBlock = ([String: Any]?) -> () diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SEProviderResponse.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SEProviderResponse.swift index 507b6194..0f28bb96 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SEProviderResponse.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SEProviderResponse.swift @@ -24,9 +24,9 @@ import Foundation import SEAuthenticatorCore public struct SEProviderResponse: SerializableResponse { + public var baseUrl: URL public let name: String public let code: String - public let connectUrl: URL public let version: String public var logoUrl: URL? public var supportEmail: String @@ -49,7 +49,7 @@ public struct SEProviderResponse: SerializableResponse { self.supportEmail = (data[SENetKeys.supportEmail] as? String) ?? "" self.name = name self.code = code - self.connectUrl = connectUrl + self.baseUrl = connectUrl self.version = version } else { return nil diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEProviderManager.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEProviderManagerV2.swift similarity index 86% rename from SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEProviderManager.swift rename to SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEProviderManagerV2.swift index 70bec8ad..a43f7352 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEProviderManager.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEProviderManagerV2.swift @@ -1,5 +1,5 @@ // -// SEProviderManager +// SEProviderManagerV2 // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) // Copyright © 2021 Salt Edge Inc. @@ -23,14 +23,14 @@ import Foundation import SEAuthenticatorCore -public struct SEProviderManager { +public struct SEProviderManagerV2 { public static func fetchProviderData( url: URL, - onSuccess success: @escaping HTTPServiceSuccessClosure, + onSuccess success: @escaping HTTPServiceSuccessClosure, onFailure failure: @escaping FailureBlock ) { - HTTPService.execute( - request: SEProviderRouter.fetchData(url), + HTTPService.execute( + request: SEProviderRouterV2.fetchData(url), success: success, failure: failure ) diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponse.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponseV2.swift similarity index 82% rename from SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponse.swift rename to SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponseV2.swift index 05df83a2..ffa4c8e0 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponse.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponseV2.swift @@ -1,5 +1,5 @@ // -// SEProviderResponse +// SEProviderResponseV2 // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) // Copyright © 2021 Salt Edge Inc. @@ -23,10 +23,10 @@ import Foundation import SEAuthenticatorCore -public struct SEProviderResponse: SerializableResponse { - public let id: String +public struct SEProviderResponseV2: SerializableResponse { public let name: String - public let scaServiceUrl: String + public var baseUrl: URL + public let providerId: Int public let apiVersion: String public var logoUrl: URL? public var supportEmail: String @@ -36,11 +36,12 @@ public struct SEProviderResponse: SerializableResponse { public init?(_ value: Any) { if let dict = value as? [String: Any], let dataDict = dict[SENetKeys.data] as? [String: Any], - let id = dataDict[SENetKeys.id] as? String, + let id = dataDict[ApiConstants.providerId] as? Int, let name = dataDict[ApiConstants.providerName] as? String, - let scaServiceUrl = dataDict[ApiConstants.scaServiceUrl] as? String, + let scaServiceUrlString = dataDict[ApiConstants.scaServiceUrl] as? String, let apiVersion = dataDict[ApiConstants.apiVersion] as? String, - let publicKey = dataDict[ApiConstants.providerPublicKey] as? String { + let publicKey = dataDict[ApiConstants.providerPublicKey] as? String, + let scaServiceUrl = URL(string: scaServiceUrlString) { if let logoUrlString = dataDict[ApiConstants.providerLogoUrl] as? String, let logoUrl = URL(string: logoUrlString) { self.logoUrl = logoUrl @@ -48,9 +49,9 @@ public struct SEProviderResponse: SerializableResponse { self.supportEmail = (dataDict[ApiConstants.providerSupportEmail] as? String) ?? "" self.geolocationRequired = dataDict[SENetKeys.geolocationRequired] as? Bool - self.id = id + self.providerId = id self.name = name - self.scaServiceUrl = scaServiceUrl + self.baseUrl = scaServiceUrl self.apiVersion = apiVersion self.publicKey = publicKey } else { diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConnectionRouter.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConnectionRouter.swift index 49926285..17bd52e2 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConnectionRouter.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConnectionRouter.swift @@ -24,11 +24,17 @@ import Foundation import SEAuthenticatorCore public struct SECreateConnectionParams { - public let providerId: String - public let returnUrl: String + public let providerId: Int public let pushToken: String? public let connectQuery: String? public let encryptedRsaPublicKey: SEEncryptedData + + public init(providerId: Int, pushToken: String?, connectQuery: String?, encryptedRsaPublicKey: SEEncryptedData) { + self.providerId = providerId + self.pushToken = pushToken + self.connectQuery = connectQuery + self.encryptedRsaPublicKey = encryptedRsaPublicKey + } } enum SEConnectionRouter: Routable { @@ -61,7 +67,8 @@ enum SEConnectionRouter: Routable { var headers: [String: String]? { switch self { - case .createConnection(_, _, let appLanguage): return Headers.requestHeaders(with: appLanguage) + case .createConnection(_, _, let appLanguage): + return Headers.requestHeaders(with: appLanguage) case .revoke(let data): return Headers.signedRequestHeaders( token: data.accessToken, diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEProviderRouter.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEProviderRouterV2.swift similarity index 92% rename from SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEProviderRouter.swift rename to SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEProviderRouterV2.swift index 5583744c..a16f84e8 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEProviderRouter.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEProviderRouterV2.swift @@ -1,5 +1,5 @@ // -// SEProviderRouter +// SEProviderRouterV2 // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) // Copyright © 2021 Salt Edge Inc. @@ -23,7 +23,7 @@ import Foundation import SEAuthenticatorCore -enum SEProviderRouter: Routable { +enum SEProviderRouterV2: Routable { case fetchData(URL) var method: HTTPMethod { @@ -41,7 +41,7 @@ enum SEProviderRouter: Routable { } var headers: [String: String]? { - return nil + return Headers.requestHeaders(with: "en") } var parameters: [String: Any]? { diff --git a/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift b/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift index bc46816b..a405711a 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift @@ -37,7 +37,7 @@ struct ParametersKeys { static let confirm = "confirm" static let authorizationCode = "authorization_code" static let credentials = "credentials" - static let encryptedRsaPublicKey = "encrypted_rsa_public_key" + static let encryptedRsaPublicKey = "enc_rsa_public_key" static let exp = "exp" } @@ -49,16 +49,21 @@ struct RequestParametersBuilder { ParametersKeys.iv: connectionParams.encryptedRsaPublicKey.iv, ] - return [ - ParametersKeys.data: [ - ParametersKeys.providerId: connectionParams.providerId, - ParametersKeys.returnUrl: SENetConstants.oauthRedirectUrl, - ParametersKeys.platform: "ios", - ParametersKeys.pushToken: connectionParams.pushToken, - ParametersKeys.encryptedRsaPublicKey: encryptedRsaPublicKeyDict, - ParametersKeys.connectQuery: connectionParams.connectQuery - ] + var data: [String: Any] = [ + ParametersKeys.providerId: connectionParams.providerId, + ParametersKeys.returnUrl: SENetConstants.oauthRedirectUrl, + ParametersKeys.platform: "ios", + ParametersKeys.encryptedRsaPublicKey: encryptedRsaPublicKeyDict ] + + if let pushToken = connectionParams.pushToken, !pushToken.isEmpty { + data = data.merge(with: [ParametersKeys.pushToken: pushToken]) + } + if let connectQuery = connectionParams.connectQuery, !connectQuery.isEmpty { + data = data.merge(with: [ParametersKeys.connectQuery: connectQuery]) + } + + return [ParametersKeys.data: data] } static func confirmAuthorizationParams(encryptedData: SEEncryptedData?, exp: Int) -> [String: Any] { From cd25d7c9a30db8f6b8537efc0d1a1578eb961813 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Wed, 26 May 2021 22:34:38 +0300 Subject: [PATCH 015/110] fixed enrollment --- .../Authenticator.xcodeproj/project.pbxproj | 12 ----- .../Connect/QRCodeCoordinator.swift | 2 - .../Main/ApplicationCoordinator.swift | 4 +- .../Models/Database/Connection.swift | 3 +- .../Utils/Handlers/ConnectHandler.swift | 12 +---- .../v1/ConnectionsInteractor.swift | 2 +- .../v2/ConnectionsInteractorV2.swift | 17 +++---- .../QRCodeViewController.swift | 3 +- Example/Tests/Crypto/SECryptoHelperSpec.swift | 45 ------------------- .../Tests/Helpers/SEConnectHelperSpec.swift | 2 +- .../Routers/SEConnectionRouterSpecV2.swift | 2 +- .../Classes/Crypto/SECryptoHelper.swift | 18 ++++---- .../API/Responses/SEProviderResponseV2.swift | 4 +- .../API/Routers/SEConnectionRouter.swift | 4 +- 14 files changed, 29 insertions(+), 101 deletions(-) delete mode 100644 Example/Tests/Crypto/SECryptoHelperSpec.swift diff --git a/Example/Authenticator.xcodeproj/project.pbxproj b/Example/Authenticator.xcodeproj/project.pbxproj index 7405e103..4c5514ae 100644 --- a/Example/Authenticator.xcodeproj/project.pbxproj +++ b/Example/Authenticator.xcodeproj/project.pbxproj @@ -156,7 +156,6 @@ 9DCBB28C243618B900DB3F85 /* Observable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCBB28A243618B900DB3F85 /* Observable.swift */; }; 9DCC5E25265D1DD40054B933 /* ConnectionsInteractorV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCC5E24265D1DD40054B933 /* ConnectionsInteractorV2.swift */; }; 9DCC5E26265D1DD40054B933 /* ConnectionsInteractorV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCC5E24265D1DD40054B933 /* ConnectionsInteractorV2.swift */; }; - 9DCC5E29265D2F150054B933 /* SECryptoHelperSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCC5E28265D2F150054B933 /* SECryptoHelperSpec.swift */; }; 9DCED89A22CE18280050ED3C /* APIErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED7DD22CE18270050ED3C /* APIErrors.swift */; }; 9DCED89C22CE18280050ED3C /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED7E122CE18270050ED3C /* SettingsViewController.swift */; }; 9DCED89E22CE18280050ED3C /* ConnectionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED7E422CE18270050ED3C /* ConnectionsViewController.swift */; }; @@ -470,7 +469,6 @@ 9DBAD7A1247E65D30023F944 /* InstantActionCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantActionCoordinator.swift; sourceTree = ""; }; 9DCBB28A243618B900DB3F85 /* Observable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Observable.swift; sourceTree = ""; }; 9DCC5E24265D1DD40054B933 /* ConnectionsInteractorV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionsInteractorV2.swift; sourceTree = ""; }; - 9DCC5E28265D2F150054B933 /* SECryptoHelperSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SECryptoHelperSpec.swift; sourceTree = ""; }; 9DCED7DD22CE18270050ED3C /* APIErrors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIErrors.swift; sourceTree = ""; }; 9DCED7E022CE18270050ED3C /* LanguagePickerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LanguagePickerViewController.swift; sourceTree = ""; }; 9DCED7E122CE18270050ED3C /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; @@ -687,7 +685,6 @@ 607FACE81AFB9204008FA782 /* Tests */ = { isa = PBXGroup; children = ( - 9DCC5E27265D2F050054B933 /* Crypto */, 9D69071A2655437E00A9CE94 /* v2 */, 9DCBB2872435F1EF00DB3F85 /* ViewModels */, 9D66E1D222D6146800BD59B6 /* Networking */, @@ -990,14 +987,6 @@ path = v1; sourceTree = ""; }; - 9DCC5E27265D2F050054B933 /* Crypto */ = { - isa = PBXGroup; - children = ( - 9DCC5E28265D2F150054B933 /* SECryptoHelperSpec.swift */, - ); - path = Crypto; - sourceTree = ""; - }; 9DCED7DC22CE18270050ED3C /* Errors */ = { isa = PBXGroup; children = ( @@ -2065,7 +2054,6 @@ 9D66E1B622D4C1F700BD59B6 /* ConnectionRepositorySpec.swift in Sources */, 9DE56A0324AA179100FA5B2C /* ConsentLogoView.swift in Sources */, 959E30A1247BC78D0067354A /* LanguagePickerViewModel.swift in Sources */, - 9DCC5E29265D2F150054B933 /* SECryptoHelperSpec.swift in Sources */, 9DE3247E22D3998D00EB162A /* PasscodeCoordinator.swift in Sources */, 9D6575F8232A9B0200DCC53E /* DateExtensionsSpec.swift in Sources */, 9DE3249E22D399F900EB162A /* DicitionaryExtensions.swift in Sources */, diff --git a/Example/Authenticator/Coordinators/Connect/QRCodeCoordinator.swift b/Example/Authenticator/Coordinators/Connect/QRCodeCoordinator.swift index d0a242e8..1c1e35a8 100644 --- a/Example/Authenticator/Coordinators/Connect/QRCodeCoordinator.swift +++ b/Example/Authenticator/Coordinators/Connect/QRCodeCoordinator.swift @@ -55,8 +55,6 @@ extension QRCodeCoordinator: QRCodeViewControllerDelegate { guard let url = URL(string: data), SEConnectHelper.isValid(deepLinkUrl: url) else { return } - let apiVersion = data.apiVerion - if let actionGuid = SEConnectHelper.actionGuid(from: url), let connectUrl = SEConnectHelper.connectUrl(from: url) { instantActionCoordinator = InstantActionCoordinator( diff --git a/Example/Authenticator/Coordinators/Main/ApplicationCoordinator.swift b/Example/Authenticator/Coordinators/Main/ApplicationCoordinator.swift index 3c75240c..e461a286 100644 --- a/Example/Authenticator/Coordinators/Main/ApplicationCoordinator.swift +++ b/Example/Authenticator/Coordinators/Main/ApplicationCoordinator.swift @@ -235,9 +235,7 @@ final class ApplicationCoordinator: Coordinator { } } - private func startConnect(url: URL, controller: UIViewController) { - let apiVersion = url.absoluteString.apiVerion - + private func startConnect(url: URL, controller: UIViewController) { if let actionGuid = SEConnectHelper.actionGuid(from: url), let connectUrl = SEConnectHelper.connectUrl(from: url) { instantActionCoordinator = InstantActionCoordinator( diff --git a/Example/Authenticator/Models/Database/Connection.swift b/Example/Authenticator/Models/Database/Connection.swift index 22683eb7..3b42f6b1 100644 --- a/Example/Authenticator/Models/Database/Connection.swift +++ b/Example/Authenticator/Models/Database/Connection.swift @@ -28,6 +28,7 @@ enum ConnectionStatus: String { case inactive } +// TODO: Add migrations @objcMembers final class Connection: Object { dynamic var id: String = "" dynamic var guid: String = UUID().uuidString @@ -42,7 +43,7 @@ enum ConnectionStatus: String { dynamic var createdAt: Date = Date() dynamic var updatedAt: Date = Date() - dynamic var providerId: Int? + dynamic var providerId: String? dynamic var publicKey: String = "" dynamic var apiVersion: String = "1" diff --git a/Example/Authenticator/Utils/Handlers/ConnectHandler.swift b/Example/Authenticator/Utils/Handlers/ConnectHandler.swift index c8e2062e..9ab0d53a 100644 --- a/Example/Authenticator/Utils/Handlers/ConnectHandler.swift +++ b/Example/Authenticator/Utils/Handlers/ConnectHandler.swift @@ -108,11 +108,7 @@ final class ConnectHandler { ConnectionsInteractorV2.createNewConnection( from: configurationUrl, with: connectQuery, - success: { [weak self] connection, accessToken in - self?.connection = connection - self?.saveConnectionAndFinish(with: accessToken) - }, - redirect: { [weak self] connection, connectUrl in + success: { [weak self] connection, connectUrl in self?.connection = connection self?.delegate?.startWebViewLoading(with: connectUrl) }, @@ -148,11 +144,7 @@ final class ConnectHandler { ConnectionsInteractorV2.submitNewConnection( for: connection, connectQuery: nil, - success: { [weak self] connection, accessToken in - self?.connection = connection - self?.saveConnectionAndFinish(with: accessToken) - }, - redirect: { [weak self] connection, connectUrl in + success: { [weak self] connection, connectUrl in self?.connection = connection self?.delegate?.startWebViewLoading(with: connectUrl) }, diff --git a/Example/Authenticator/Utils/Interactors/v1/ConnectionsInteractor.swift b/Example/Authenticator/Utils/Interactors/v1/ConnectionsInteractor.swift index e3cfa878..4483e765 100644 --- a/Example/Authenticator/Utils/Interactors/v1/ConnectionsInteractor.swift +++ b/Example/Authenticator/Utils/Interactors/v1/ConnectionsInteractor.swift @@ -117,7 +117,7 @@ struct ConnectionsInteractor { entityId: connection.id ) - SEAuthenticator.SEConnectionManager.revokeConnection( + SEConnectionManager.revokeConnection( data: data, onSuccess: { _ in success?() diff --git a/Example/Authenticator/Utils/Interactors/v2/ConnectionsInteractorV2.swift b/Example/Authenticator/Utils/Interactors/v2/ConnectionsInteractorV2.swift index d25e2223..9a1815b0 100644 --- a/Example/Authenticator/Utils/Interactors/v2/ConnectionsInteractorV2.swift +++ b/Example/Authenticator/Utils/Interactors/v2/ConnectionsInteractorV2.swift @@ -32,8 +32,7 @@ struct ConnectionsInteractorV2 { static func createNewConnection( from url: URL, with connectQuery: String?, - success: @escaping (Connection, AccessToken) -> (), - redirect: @escaping (Connection, String) -> (), + success: @escaping (Connection, String) -> (), failure: @escaping (String) -> () ) { getProviderConfiguration( @@ -59,7 +58,6 @@ struct ConnectionsInteractorV2 { for: connection, connectQuery: connectQuery, success: success, - redirect: redirect, failure: failure ) }, @@ -86,8 +84,7 @@ struct ConnectionsInteractorV2 { static func submitNewConnection( for connection: Connection, connectQuery: String?, - success: @escaping (Connection, AccessToken) -> (), - redirect: @escaping (Connection, String) -> (), + success: @escaping (Connection, String) -> (), failure: @escaping (String) -> () ) { // 1. Create Provider's public key (SecKey) @@ -122,14 +119,12 @@ struct ConnectionsInteractorV2 { SEConnectionManager.createConnection( by: connectUrl, params: params, - appLanguage: "en", + appLanguage: UserDefaultsHelper.applicationLanguage, onSuccess: { response in - // TODO: Finish - print(response) + connection.id = "\(response.id)" + success(connection, response.authenticationUrl) }, - onFailure: { error in - print(error) - } + onFailure: failure ) } } diff --git a/Example/Authenticator/View Controllers/QRCodeViewController.swift b/Example/Authenticator/View Controllers/QRCodeViewController.swift index 1d084082..cdd0a3d6 100644 --- a/Example/Authenticator/View Controllers/QRCodeViewController.swift +++ b/Example/Authenticator/View Controllers/QRCodeViewController.swift @@ -142,8 +142,7 @@ final class QRCodeViewController: BaseViewController { } @objc private func cancelPressed() { - dismiss(animated: true) - shouldDismissClosure?() + dismiss(animated: true, completion: shouldDismissClosure) } private func labelsStackView() -> UIStackView { diff --git a/Example/Tests/Crypto/SECryptoHelperSpec.swift b/Example/Tests/Crypto/SECryptoHelperSpec.swift deleted file mode 100644 index e80108b0..00000000 --- a/Example/Tests/Crypto/SECryptoHelperSpec.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// SECryptoHelperSpec -// This file is part of the Salt Edge Authenticator distribution -// (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2021 Salt Edge Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 3 or later. -// -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -// For the additional permissions granted for Salt Edge Authenticator -// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md -// - -import Quick -import Nimble -@testable import SEAuthenticatorCore - -class SECryptoHelperSpec: BaseSpec { - override func spec() { - fdescribe("publicKeyToPem") { - it("should do") { -// print(SpecUtils.publicKeyPem.pemToPublicKey) -// let tag = "test" -// let key = try? SECryptoHelper.createKey(from: DataFixtures.publicKey, isPublic: true, tag: tag) - -// let expectedPem = try! SECryptoHelper.publicKeyData(for: tag).string -// expect(specu).to(equal(SpecUtils.publicKeyPem)) - } - -// let tag = SETagHelper.create(for: "test") -// _ = SECryptoHelper.createKeyPair(with: tag) -// -// print(SECryptoHelper.publicKeyToPem(tag: tag)) - } - } -} diff --git a/Example/Tests/Helpers/SEConnectHelperSpec.swift b/Example/Tests/Helpers/SEConnectHelperSpec.swift index 9d80c011..edabd164 100644 --- a/Example/Tests/Helpers/SEConnectHelperSpec.swift +++ b/Example/Tests/Helpers/SEConnectHelperSpec.swift @@ -22,7 +22,7 @@ import Quick import Nimble -@testable import SEAuthenticator +@testable import SEAuthenticatorCore class SEConnectHelperSpec: BaseSpec { override func spec() { diff --git a/Example/Tests/v2/Networking/Routers/SEConnectionRouterSpecV2.swift b/Example/Tests/v2/Networking/Routers/SEConnectionRouterSpecV2.swift index 9efd8f5d..5cd2a8f5 100644 --- a/Example/Tests/v2/Networking/Routers/SEConnectionRouterSpecV2.swift +++ b/Example/Tests/v2/Networking/Routers/SEConnectionRouterSpecV2.swift @@ -35,7 +35,7 @@ class SEConnectionRouterSpecV2: BaseSpec { it("should create a valid url request") { let encryptedData = SEEncryptedData(data: "data", key: "key", iv: "iv") let data = SECreateConnectionParams( - providerId: 12, + providerId: "12", pushToken: "push_token", connectQuery: "connect_query", encryptedRsaPublicKey: encryptedData diff --git a/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelper.swift b/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelper.swift index c68bd411..1d39c7b3 100644 --- a/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelper.swift +++ b/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelper.swift @@ -50,9 +50,7 @@ public struct SECryptoHelper { - tag: An unique identifier by which was created the private key */ public static func privateKeyToPem(tag: KeyTag) -> String? { - guard let encodedKey = try? privateKeyData(for: tag.privateTag).base64EncodedString() else { return nil } - - return "-----BEGIN PRIVATE KEY-----\n\(encodedKey)\n-----END PRIVATE KEY-----\n" + return try? privateKeyData(for: tag.privateTag).string } /* @@ -62,9 +60,7 @@ public struct SECryptoHelper { - tag: An unique identifier by which was created the public key */ public static func publicKeyToPem(tag: KeyTag) -> String? { - guard let encodedKey = try? privateKeyData(for: tag).base64EncodedString() else { return nil } - - return "-----BEGIN PUBLIC KEY-----\n\(encodedKey)\n-----END PUBLIC KEY-----\n" + return try? publicKeyData(for: tag).string } /* @@ -164,7 +160,10 @@ public struct SECryptoHelper { idx += blockSize } - return Data(bytes: UnsafePointer(decryptedDataBytes), count: decryptedDataBytes.count) + let uint8Pointer = UnsafeMutablePointer.allocate(capacity: decryptedDataBytes.count) + uint8Pointer.initialize(from: &decryptedDataBytes, count: decryptedDataBytes.count) + + return Data(bytes: uint8Pointer, count: decryptedDataBytes.count) } private static func publicEncrypt(data: Data, keyForTag: KeyTag) throws -> Data { @@ -197,7 +196,10 @@ public struct SECryptoHelper { idx += maxChunkSize } - return Data(bytes: UnsafePointer(encryptedDataBytes), count: encryptedDataBytes.count) + let uint8Pointer = UnsafeMutablePointer.allocate(capacity: encryptedDataBytes.count) + uint8Pointer.initialize(from: &encryptedDataBytes, count: encryptedDataBytes.count) + + return Data(bytes: uint8Pointer, count: encryptedDataBytes.count) } private static func generateRandomBytes(count: Int) throws -> Data { diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponseV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponseV2.swift index ffa4c8e0..01552c8f 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponseV2.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponseV2.swift @@ -26,7 +26,7 @@ import SEAuthenticatorCore public struct SEProviderResponseV2: SerializableResponse { public let name: String public var baseUrl: URL - public let providerId: Int + public let providerId: String public let apiVersion: String public var logoUrl: URL? public var supportEmail: String @@ -36,7 +36,7 @@ public struct SEProviderResponseV2: SerializableResponse { public init?(_ value: Any) { if let dict = value as? [String: Any], let dataDict = dict[SENetKeys.data] as? [String: Any], - let id = dataDict[ApiConstants.providerId] as? Int, + let id = dataDict[ApiConstants.providerId] as? String, let name = dataDict[ApiConstants.providerName] as? String, let scaServiceUrlString = dataDict[ApiConstants.scaServiceUrl] as? String, let apiVersion = dataDict[ApiConstants.apiVersion] as? String, diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConnectionRouter.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConnectionRouter.swift index 17bd52e2..f17099d2 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConnectionRouter.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConnectionRouter.swift @@ -24,12 +24,12 @@ import Foundation import SEAuthenticatorCore public struct SECreateConnectionParams { - public let providerId: Int + public let providerId: String public let pushToken: String? public let connectQuery: String? public let encryptedRsaPublicKey: SEEncryptedData - public init(providerId: Int, pushToken: String?, connectQuery: String?, encryptedRsaPublicKey: SEEncryptedData) { + public init(providerId: String, pushToken: String?, connectQuery: String?, encryptedRsaPublicKey: SEEncryptedData) { self.providerId = providerId self.pushToken = pushToken self.connectQuery = connectQuery From 65701111380dcbdce7707dc6e5d5f610574a6673 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Thu, 27 May 2021 15:17:44 +0300 Subject: [PATCH 016/110] fixed version extractor --- .../Classes/Helpers/ApiVersionExtractor.swift | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/SaltedgeAuthenticatorCore/Classes/Helpers/ApiVersionExtractor.swift b/SaltedgeAuthenticatorCore/Classes/Helpers/ApiVersionExtractor.swift index 28a0e8d3..6b34d101 100644 --- a/SaltedgeAuthenticatorCore/Classes/Helpers/ApiVersionExtractor.swift +++ b/SaltedgeAuthenticatorCore/Classes/Helpers/ApiVersionExtractor.swift @@ -26,10 +26,9 @@ public extension String { var apiVerion: String { let segments = split(separator: "/") - guard let index = segments.firstIndex(of: "authenticator") else { return "1" } + guard let index = segments.firstIndex(of: "authenticator"), + let apiVersionValue = Int(segments[index + 1].replacingOccurrences(of: "v", with: "")) else { return "1" } - let apiVersionValue = segments[index + 1].replacingOccurrences(of: "v", with: "") - - return apiVersionValue.isEmpty ? "1" : apiVersionValue + return "\(apiVersionValue)" } } From e2bed53cf77cbacde52c32f41740af350938232c Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Fri, 28 May 2021 18:01:35 +0300 Subject: [PATCH 017/110] added revoke connection v2 and base connections interactor --- .../Authenticator.xcodeproj/project.pbxproj | 14 +++ .../Connections/ConnectionsCoordinator.swift | 4 + .../Models/Database/Connection.swift | 8 ++ .../Utils/Handlers/ConnectHandler.swift | 107 +++++++----------- .../Base/BaseConnectionsInteractor.swift | 42 +++++++ .../v1/ConnectionsInteractor.swift | 17 ++- .../v2/ConnectionsInteractorV2.swift | 47 ++++++-- .../Connections/ConnectionsViewModel.swift | 25 +++- ...ager.swift => SEConnectionManagerV2.swift} | 4 +- .../API/Routers/SEConnectionRouter.swift | 2 +- 10 files changed, 181 insertions(+), 89 deletions(-) create mode 100644 Example/Authenticator/Utils/Interactors/Base/BaseConnectionsInteractor.swift rename SaltedgeAuthenticatorSDKv2/Classes/API/Managers/{SEConnectionManager.swift => SEConnectionManagerV2.swift} (96%) diff --git a/Example/Authenticator.xcodeproj/project.pbxproj b/Example/Authenticator.xcodeproj/project.pbxproj index 4c5514ae..b0e09287 100644 --- a/Example/Authenticator.xcodeproj/project.pbxproj +++ b/Example/Authenticator.xcodeproj/project.pbxproj @@ -228,6 +228,8 @@ 9DCED92D22CE1DC30050ED3C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9DCED92B22CE1DC30050ED3C /* LaunchScreen.storyboard */; }; 9DCED93722D3389C0050ED3C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9DCED93622D3389C0050ED3C /* Assets.xcassets */; }; 9DD155EC265E74C800AEFA0D /* ApiVersionExtractorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD155EB265E74C800AEFA0D /* ApiVersionExtractorSpec.swift */; }; + 9DD155F326613CEA00AEFA0D /* BaseConnectionsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD155F226613CEA00AEFA0D /* BaseConnectionsInteractor.swift */; }; + 9DD155F426613CEB00AEFA0D /* BaseConnectionsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD155F226613CEA00AEFA0D /* BaseConnectionsInteractor.swift */; }; 9DD4DF252375969A000A9B80 /* QuickActionsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD4DF242375969A000A9B80 /* QuickActionsHelper.swift */; }; 9DD4DF262375969B000A9B80 /* QuickActionsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD4DF242375969A000A9B80 /* QuickActionsHelper.swift */; }; 9DD4DF2823759767000A9B80 /* AVCaptureHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD4DF2723759767000A9B80 /* AVCaptureHelper.swift */; }; @@ -549,6 +551,7 @@ 9DCED92C22CE1DC30050ED3C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 9DCED93622D3389C0050ED3C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Authenticator/Assets.xcassets; sourceTree = SOURCE_ROOT; }; 9DD155EB265E74C800AEFA0D /* ApiVersionExtractorSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiVersionExtractorSpec.swift; sourceTree = ""; }; + 9DD155F226613CEA00AEFA0D /* BaseConnectionsInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseConnectionsInteractor.swift; sourceTree = ""; }; 9DD4DF242375969A000A9B80 /* QuickActionsHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickActionsHelper.swift; sourceTree = ""; }; 9DD4DF2723759767000A9B80 /* AVCaptureHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVCaptureHelper.swift; sourceTree = ""; }; 9DD50990247527D800CB188D /* AuthorizationsViewModelSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorizationsViewModelSpec.swift; sourceTree = ""; }; @@ -1109,6 +1112,7 @@ 9DCED82B22CE18270050ED3C /* Interactors */ = { isa = PBXGroup; children = ( + 9DD155F126613CD800AEFA0D /* Base */, 9DCC5E23265D1DBD0054B933 /* v1 */, 9DCC5E22265D1DB70054B933 /* v2 */, ); @@ -1335,6 +1339,14 @@ path = Localization; sourceTree = ""; }; + 9DD155F126613CD800AEFA0D /* Base */ = { + isa = PBXGroup; + children = ( + 9DD155F226613CEA00AEFA0D /* BaseConnectionsInteractor.swift */, + ); + path = Base; + sourceTree = ""; + }; 9DE3242822D396F200EB162A /* TestHost-iOS */ = { isa = PBXGroup; children = ( @@ -1891,6 +1903,7 @@ 9DCED90322CE18280050ED3C /* TaptileFeedbackButton.swift in Sources */, 9DCED8E022CE18280050ED3C /* UserDefaultsHelper.swift in Sources */, 9DCC5E25265D1DD40054B933 /* ConnectionsInteractorV2.swift in Sources */, + 9DD155F326613CEA00AEFA0D /* BaseConnectionsInteractor.swift in Sources */, 9DCED8D522CE18280050ED3C /* ApplicationExtensions.swift in Sources */, 9DCED8D622CE18280050ED3C /* ViewExtensions+Animations.swift in Sources */, 9DCED89C22CE18280050ED3C /* SettingsViewController.swift in Sources */, @@ -1995,6 +2008,7 @@ 9DE324C222D39A6D00EB162A /* OnboardingViewController.swift in Sources */, 959E30AA247BD0170067354A /* LicensesViewModel.swift in Sources */, 959E30A6247BCBB90067354A /* LicensesCoordinator.swift in Sources */, + 9DD155F426613CEB00AEFA0D /* BaseConnectionsInteractor.swift in Sources */, 9DE324E022D39ACB00EB162A /* LoadingIndicator.swift in Sources */, 9DE3249D22D399F600EB162A /* StackViewExtensions.swift in Sources */, 9D77212A231E50400015BAC6 /* AuthorizationsHeaderCollectionViewCell.swift in Sources */, diff --git a/Example/Authenticator/Coordinators/Connections/ConnectionsCoordinator.swift b/Example/Authenticator/Coordinators/Connections/ConnectionsCoordinator.swift index 4e9e7770..2e601d44 100644 --- a/Example/Authenticator/Coordinators/Connections/ConnectionsCoordinator.swift +++ b/Example/Authenticator/Coordinators/Connections/ConnectionsCoordinator.swift @@ -112,4 +112,8 @@ extension ConnectionsCoordinator: ConnectionsEventsDelegate { connectViewCoordinator = ConnectViewCoordinator(rootViewController: currentViewController, connectionType: .reconnect(id)) connectViewCoordinator?.start() } + + func presentError(_ error: String) { + currentViewController.present(message: error) + } } diff --git a/Example/Authenticator/Models/Database/Connection.swift b/Example/Authenticator/Models/Database/Connection.swift index 3b42f6b1..4b51d3b8 100644 --- a/Example/Authenticator/Models/Database/Connection.swift +++ b/Example/Authenticator/Models/Database/Connection.swift @@ -64,4 +64,12 @@ extension Connection { var isManaged: Bool { return realm != nil } + + var isApiV2: Bool { + return apiVersion == "2" + } + + var providerPublicKeyTag: String { + return "\(guid)_provider_public_key" + } } diff --git a/Example/Authenticator/Utils/Handlers/ConnectHandler.swift b/Example/Authenticator/Utils/Handlers/ConnectHandler.swift index 9ab0d53a..16117aa3 100644 --- a/Example/Authenticator/Utils/Handlers/ConnectHandler.swift +++ b/Example/Authenticator/Utils/Handlers/ConnectHandler.swift @@ -82,77 +82,58 @@ final class ConnectHandler { let connectQuery = SEConnectHelper.connectQuery(from: url) delegate?.showWebViewController() - createNewConnection(from: configurationUrl, with: connectQuery) - } - private func createNewConnection(from configurationUrl: URL, with connectQuery: String?) { let apiVersion = configurationUrl.absoluteString.apiVerion + createNewConnection( + from: configurationUrl, + with: connectQuery, + interactor: apiVersion == "2" ? ConnectionsInteractorV2() : ConnectionsInteractor() + ) + } - if apiVersion == "1" { - ConnectionsInteractor.createNewConnection( - from: configurationUrl, - with: connectQuery, - success: { [weak self] connection, accessToken in - self?.connection = connection - self?.saveConnectionAndFinish(with: accessToken) - }, - redirect: { [weak self] connection, connectUrl in - self?.connection = connection - self?.delegate?.startWebViewLoading(with: connectUrl) - }, - failure: { [weak self] error in - self?.dismissConnectWithError(error: error) - } - ) - } else { - ConnectionsInteractorV2.createNewConnection( - from: configurationUrl, - with: connectQuery, - success: { [weak self] connection, connectUrl in - self?.connection = connection - self?.delegate?.startWebViewLoading(with: connectUrl) - }, - failure: { [weak self] error in - self?.dismissConnectWithError(error: error) - } - ) - } + private func createNewConnection( + from configurationUrl: URL, + with connectQuery: String?, + interactor: BaseConnectionsInteractor + ) { + interactor.createNewConnection( + from: configurationUrl, + with: connectQuery, + success: { [weak self] connection, accessToken in + self?.connection = connection + self?.saveConnectionAndFinish(with: accessToken) + }, + redirect: { [weak self] connection, connectUrl in + self?.connection = connection + self?.delegate?.startWebViewLoading(with: connectUrl) + }, + failure: { [weak self] error in + self?.dismissConnectWithError(error: error) + } + ) } private func reconnectConnection(_ connectionId: String) { guard let connection = ConnectionsCollector.with(id: connectionId) else { return } - let apiVersion = connection.baseUrlString.apiVerion - - if apiVersion == "1" { - ConnectionsInteractor.submitNewConnection( - for: connection, - connectQuery: nil, - success: { [weak self] connection, accessToken in - self?.connection = connection - self?.saveConnectionAndFinish(with: accessToken) - }, - redirect: { [weak self] connection, connectUrl in - self?.connection = connection - self?.delegate?.startWebViewLoading(with: connectUrl) - }, - failure: { [weak self] error in - self?.dismissConnectWithError(error: error) - } - ) - } else { - ConnectionsInteractorV2.submitNewConnection( - for: connection, - connectQuery: nil, - success: { [weak self] connection, connectUrl in - self?.connection = connection - self?.delegate?.startWebViewLoading(with: connectUrl) - }, - failure: { [weak self] error in - self?.dismissConnectWithError(error: error) - } - ) - } + let interactor: BaseConnectionsInteractor = connection.isApiV2 + ? ConnectionsInteractorV2() : ConnectionsInteractor() + + interactor.submitNewConnection( + for: connection, + connectQuery: nil, + success: { [weak self] connection, accessToken in + self?.connection = connection + self?.saveConnectionAndFinish(with: accessToken) + }, + redirect: { [weak self] connection, connectUrl in + self?.connection = connection + self?.delegate?.startWebViewLoading(with: connectUrl) + }, + failure: { [weak self] error in + self?.dismissConnectWithError(error: error) + } + ) } private func dismissConnectWithError(error: String) { diff --git a/Example/Authenticator/Utils/Interactors/Base/BaseConnectionsInteractor.swift b/Example/Authenticator/Utils/Interactors/Base/BaseConnectionsInteractor.swift new file mode 100644 index 00000000..d0198028 --- /dev/null +++ b/Example/Authenticator/Utils/Interactors/Base/BaseConnectionsInteractor.swift @@ -0,0 +1,42 @@ +// +// BaseConnectionsInteractor +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +protocol BaseConnectionsInteractor { + func createNewConnection( + from url: URL, + with connectQuery: String?, + success: @escaping (Connection, AccessToken) -> (), + redirect: @escaping (Connection, String) -> (), + failure: @escaping (String) -> () + ) + func submitNewConnection( + for connection: Connection, + connectQuery: String?, + success: @escaping (Connection, AccessToken) -> (), + redirect: @escaping (Connection, String) -> (), + failure: @escaping (String) -> () + ) + func revoke(_ connection: Connection, success: (() -> ())?, failure: @escaping (String) -> ()) +} diff --git a/Example/Authenticator/Utils/Interactors/v1/ConnectionsInteractor.swift b/Example/Authenticator/Utils/Interactors/v1/ConnectionsInteractor.swift index 4483e765..3b6daea8 100644 --- a/Example/Authenticator/Utils/Interactors/v1/ConnectionsInteractor.swift +++ b/Example/Authenticator/Utils/Interactors/v1/ConnectionsInteractor.swift @@ -24,8 +24,8 @@ import Foundation import SEAuthenticator import SEAuthenticatorCore -struct ConnectionsInteractor { - static func createNewConnection( +struct ConnectionsInteractor: BaseConnectionsInteractor { + func createNewConnection( from url: URL, with connectQuery: String?, success: @escaping (Connection, AccessToken) -> (), @@ -61,7 +61,7 @@ struct ConnectionsInteractor { ) } - static func fetchProviderConfiguration( + func fetchProviderConfiguration( from url: URL, success: @escaping (SEProviderResponse) -> (), failure: @escaping (String) -> () @@ -73,7 +73,7 @@ struct ConnectionsInteractor { ) } - static func submitNewConnection( + func submitNewConnection( for connection: Connection, connectQuery: String?, success: @escaping (Connection, AccessToken) -> (), @@ -103,9 +103,10 @@ struct ConnectionsInteractor { ) } - static func revoke( + func revoke( _ connection: Connection, - success: (() -> ())? = nil + success: (() -> ())?, + failure: @escaping (String) -> () ) { guard let baseUrl = connection.baseUrl else { return } @@ -122,9 +123,7 @@ struct ConnectionsInteractor { onSuccess: { _ in success?() }, - onFailure: { error in - Log.debugLog(message: error) - } + onFailure: failure ) } } diff --git a/Example/Authenticator/Utils/Interactors/v2/ConnectionsInteractorV2.swift b/Example/Authenticator/Utils/Interactors/v2/ConnectionsInteractorV2.swift index 9a1815b0..f9d17a57 100644 --- a/Example/Authenticator/Utils/Interactors/v2/ConnectionsInteractorV2.swift +++ b/Example/Authenticator/Utils/Interactors/v2/ConnectionsInteractorV2.swift @@ -24,15 +24,16 @@ import Foundation import SEAuthenticatorV2 import SEAuthenticatorCore -struct ConnectionsInteractorV2 { +struct ConnectionsInteractorV2: BaseConnectionsInteractor { /* Request to create new SCA Service connection. Result is returned through callback. */ - static func createNewConnection( + func createNewConnection( from url: URL, with connectQuery: String?, - success: @escaping (Connection, String) -> (), + success: @escaping (Connection, AccessToken) -> (), + redirect: @escaping (Connection, String) -> (), failure: @escaping (String) -> () ) { getProviderConfiguration( @@ -58,6 +59,7 @@ struct ConnectionsInteractorV2 { for: connection, connectQuery: connectQuery, success: success, + redirect: redirect, failure: failure ) }, @@ -69,7 +71,7 @@ struct ConnectionsInteractorV2 { Request to get SCA Service connection. Result is returned through callback. */ - static func getProviderConfiguration( + func getProviderConfiguration( from url: URL, success: @escaping (SEProviderResponseV2) -> (), failure: @escaping (String) -> () @@ -81,17 +83,18 @@ struct ConnectionsInteractorV2 { ) } - static func submitNewConnection( + func submitNewConnection( for connection: Connection, connectQuery: String?, - success: @escaping (Connection, String) -> (), + success: @escaping (Connection, AccessToken) -> (), + redirect: @escaping (Connection, String) -> (), failure: @escaping (String) -> () ) { // 1. Create Provider's public key (SecKey) SECryptoHelper.createKey( from: connection.publicKey, isPublic: true, - tag: "\(connection.guid)_provider_public_key" + tag: connection.providerPublicKeyTag ) // 2. Generate new RSA key pair for a new Connection by tag @@ -102,7 +105,7 @@ struct ConnectionsInteractorV2 { // 4. Encrypt Connection's public key with Provider's public key (step 1) let encryptedData = try? SECryptoHelper.encrypt( connectionPublicKeyPem, - tag: "\(connection.guid)_provider_public_key" + tag: connection.providerPublicKeyTag ), let providerId = connection.providerId else { return } @@ -116,13 +119,37 @@ struct ConnectionsInteractorV2 { guard let connectUrl = connection.baseUrl else { return } // 5. Send request - SEConnectionManager.createConnection( + SEConnectionManagerV2.createConnection( by: connectUrl, params: params, appLanguage: UserDefaultsHelper.applicationLanguage, onSuccess: { response in connection.id = "\(response.id)" - success(connection, response.authenticationUrl) + redirect(connection, response.authenticationUrl) + }, + onFailure: failure + ) + } + + func revoke( + _ connection: Connection, + success: (() -> ())?, + failure: @escaping (String) -> () + ) { + guard let baseUrl = connection.baseUrl else { return } + + let data = SEBaseAuthenticatedWithIdRequestData( + url: baseUrl, + connectionGuid: connection.guid, + accessToken: connection.accessToken, + appLanguage: UserDefaultsHelper.applicationLanguage, + entityId: connection.id + ) + + SEConnectionManagerV2.revokeConnection( + data: data, + onSuccess: { _ in + success?() }, onFailure: failure ) diff --git a/Example/Authenticator/View Models/Connections/ConnectionsViewModel.swift b/Example/Authenticator/View Models/Connections/ConnectionsViewModel.swift index 21ab1e52..66c0ddf7 100644 --- a/Example/Authenticator/View Models/Connections/ConnectionsViewModel.swift +++ b/Example/Authenticator/View Models/Connections/ConnectionsViewModel.swift @@ -33,6 +33,7 @@ protocol ConnectionsEventsDelegate: class { func consentsPressed(connectionId: String, consents: [SEConsentData]) func updateViews() func addPressed() + func presentError(_ error: String) } final class ConnectionsViewModel { @@ -90,8 +91,18 @@ final class ConnectionsViewModel { delegate?.updateViews() } - private func remove(connection: Connection) { - ConnectionsInteractor.revoke(connection) + private func revoke(connection: Connection, interactor: BaseConnectionsInteractor) { + interactor.revoke( + connection, + success: { self.deleteConnection(connection: connection) }, + failure: { error in + self.delegate?.presentError(error) + } + ) + } + + private func deleteConnection(connection: Connection) { + SECryptoHelper.deleteKeyPair(with: connection.providerPublicKeyTag) SECryptoHelper.deleteKeyPair(with: SETagHelper.create(for: connection.guid)) ConnectionRepository.delete(connection) } @@ -113,13 +124,19 @@ extension ConnectionsViewModel { func remove(at indexPath: IndexPath) { guard let connection = item(for: indexPath) else { return } - remove(connection: connection) + revoke( + connection: connection, + interactor: connection.isApiV2 ? ConnectionsInteractorV2() : ConnectionsInteractor() + ) } func remove(by id: ID) { guard let connection = ConnectionsCollector.with(id: id) else { return } - remove(connection: connection) + revoke( + connection: connection, + interactor: connection.isApiV2 ? ConnectionsInteractorV2() : ConnectionsInteractor() + ) } func updateName(by id: ID) { diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConnectionManager.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConnectionManagerV2.swift similarity index 96% rename from SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConnectionManager.swift rename to SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConnectionManagerV2.swift index e4d9d650..907e061e 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConnectionManager.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConnectionManagerV2.swift @@ -1,5 +1,5 @@ // -// SEConnectionManager +// SEConnectionManagerV2 // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) // Copyright © 2021 Salt Edge Inc. @@ -23,7 +23,7 @@ import Foundation import SEAuthenticatorCore -public struct SEConnectionManager { +public struct SEConnectionManagerV2 { public static func createConnection( by url: URL, params: SECreateConnectionParams, diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConnectionRouter.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConnectionRouter.swift index f17099d2..92b56114 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConnectionRouter.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConnectionRouter.swift @@ -44,7 +44,7 @@ enum SEConnectionRouter: Routable { var method: HTTPMethod { switch self { case .createConnection: return .post - case .revoke: return .delete + case .revoke: return .put } } From 8739eb4b0f9d04a7141cb02dba51571abfc639a3 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Mon, 31 May 2021 11:31:49 +0300 Subject: [PATCH 018/110] fixed specs --- .../Tests/v2/Networking/Routers/SEConnectionRouterSpecV2.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Example/Tests/v2/Networking/Routers/SEConnectionRouterSpecV2.swift b/Example/Tests/v2/Networking/Routers/SEConnectionRouterSpecV2.swift index 5cd2a8f5..b2cdc5be 100644 --- a/Example/Tests/v2/Networking/Routers/SEConnectionRouterSpecV2.swift +++ b/Example/Tests/v2/Networking/Routers/SEConnectionRouterSpecV2.swift @@ -73,7 +73,7 @@ class SEConnectionRouterSpecV2: BaseSpec { let expectedRequest = URLRequestBuilder.buildUrlRequest( with: baseUrl.appendingPathComponent("/api/authenticator/v2/connections/\(entityId)/revoke"), - method: HTTPMethod.delete.rawValue, + method: HTTPMethod.put.rawValue, headers: headers, params: parameters ) From 5cb4b288b0ed2d9923d184f86067b0357dd2b45a Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Mon, 31 May 2021 11:39:19 +0300 Subject: [PATCH 019/110] added connection spec --- .../Models/Database/ConnectionSpec.swift | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Example/Tests/Models/Database/ConnectionSpec.swift b/Example/Tests/Models/Database/ConnectionSpec.swift index bd5857d0..1574928b 100644 --- a/Example/Tests/Models/Database/ConnectionSpec.swift +++ b/Example/Tests/Models/Database/ConnectionSpec.swift @@ -60,5 +60,25 @@ class ConnectionSpec: BaseSpec { expect(connection.isManaged).to(beTrue()) } } + + describe("isApiV2") { + it("should return true if apiVersion value is 2") { + let connection = Connection() + + expect(connection.isApiV2).to(beFalse()) + + connection.apiVersion = "2" + + expect(connection.isApiV2).to(beTrue()) + } + } + + describe("providerPublicKeyTag") { + it("should return connection's provider public key tag") { + let connection = Connection() + + expect("\(connection.guid)_provider_public_key").to(equal(connection.providerPublicKeyTag)) + } + } } } From a620a9c42a35b3955d8a2e5470ad1c5146479bb0 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Thu, 3 Jun 2021 18:04:54 +0300 Subject: [PATCH 020/110] remade authorizations data source to receive v2 and v1 authorizations --- .../AuthorizationsDataSource.swift | 57 ++++++++++----- .../SEEncryptedDataExtensions.swift | 18 ++++- .../v1/AuthorizationsInteractor.swift | 61 +++++++++++----- .../v1/CollectionsInteractor.swift | 45 ++++++++---- .../AuthorizationDetailViewModel.swift | 15 +++- .../AuthorizationsViewModel.swift | 13 ++-- .../SingleAuthorizationViewModel.swift | 1 + .../API/Models/SEBaseAuthorizationData.swift | 32 +++++++++ .../SEBaseEncryptedAuthorizationData.swift | 30 ++++++++ .../Classes/API/SEEncryptedData.swift | 2 +- .../Classes/Crypto/SECryptoHelper.swift | 6 +- .../Classes/Helpers/DateUtils.swift | 4 +- .../Classes/Helpers/SEPollingTimer.swift | 5 +- .../API/Models/SEAuthorizationData.swift | 9 +-- .../Classes/Helpers/SignatureHelper.swift | 12 ++-- ...r.swift => SEAuthorizationManagerV2.swift} | 7 +- .../API/Models/SEAuthorizationDataV2.swift | 71 +++++++++++++++++++ .../API/Routers/SEAuthorizationRouter.swift | 10 +-- .../API/SEEncryptedAuthorizationData.swift | 12 ++-- 19 files changed, 315 insertions(+), 95 deletions(-) create mode 100644 SaltedgeAuthenticatorCore/Classes/API/Models/SEBaseAuthorizationData.swift create mode 100644 SaltedgeAuthenticatorCore/Classes/API/Models/SEBaseEncryptedAuthorizationData.swift rename {SaltedgeAuthenticatorSDK => SaltedgeAuthenticatorCore}/Classes/Helpers/DateUtils.swift (96%) rename {SaltedgeAuthenticatorSDK => SaltedgeAuthenticatorCore}/Classes/Helpers/SEPollingTimer.swift (96%) rename SaltedgeAuthenticatorSDKv2/Classes/API/Managers/{SEAuthorizationManager.swift => SEAuthorizationManagerV2.swift} (94%) create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEAuthorizationDataV2.swift diff --git a/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift b/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift index 3a3dbd7d..3d1b8bc3 100644 --- a/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift +++ b/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift @@ -22,9 +22,10 @@ import UIKit import SEAuthenticator +import SEAuthenticatorV2 +import SEAuthenticatorCore final class AuthorizationsDataSource { - private var authorizationResponses = [SEAuthorizationData]() private var viewModels = [AuthorizationDetailViewModel]() private var locationManagement: LocationManagement @@ -32,17 +33,37 @@ final class AuthorizationsDataSource { self.locationManagement = locationManagement } - func update(with authorizationResponses: [SEAuthorizationData]) -> Bool { - if authorizationResponses != self.authorizationResponses { - self.authorizationResponses = authorizationResponses - self.viewModels = authorizationResponses.compactMap { response in - guard response.expiresAt >= Date() else { return nil } + func update(with baseData: [SEBaseAuthorizationData]) -> Bool { + let viewModelsV1 = baseData.filter { $0.apiVersion == "1" }.compactMap { response in + guard response.expiresAt >= Date() else { return nil } - let connection = ConnectionsCollector.with(id: response.connectionId) - let showLocationWarning = locationManagement.showLocationWarning(connection: connection) + let connection = ConnectionsCollector.with(id: response.connectionId) + let showLocationWarning = locationManagement.showLocationWarning(connection: connection) - return AuthorizationDetailViewModel(response, showLocationWarning: showLocationWarning) - }.merge(array: self.viewModels).sorted(by: { $0.createdAt < $1.createdAt }) + return AuthorizationDetailViewModel( + response, + apiVersion: response.apiVersion, + showLocationWarning: showLocationWarning + ) + }.merge(array: self.viewModels.filter { $0.apiVersion == "1" }) + + let viewModelsV2 = baseData.filter { $0.apiVersion == "2" }.compactMap { response in + guard response.expiresAt >= Date() else { return nil } + + let connection = ConnectionsCollector.with(id: response.connectionId) + let showLocationWarning = locationManagement.showLocationWarning(connection: connection) + + return AuthorizationDetailViewModel( + response, + apiVersion: response.apiVersion, + showLocationWarning: showLocationWarning + ) + }.merge(array: self.viewModels.filter { $0.apiVersion == "2" }) + + let allViewModels = viewModelsV1 + viewModelsV2 + + if allViewModels != self.viewModels { + self.viewModels = (viewModelsV1 + viewModelsV2).sorted(by: { $0.createdAt < $1.createdAt }) return true } @@ -107,7 +128,6 @@ final class AuthorizationsDataSource { } func clearAuthorizations() { - authorizationResponses.removeAll() viewModels.removeAll() } @@ -130,20 +150,19 @@ final class AuthorizationsDataSource { private extension Array where Element == AuthorizationDetailViewModel { func merge(array: [Element]) -> [AuthorizationDetailViewModel] { - let finalElements: [Element] = array.compactMap { element in - if element.expired || element.state.value != .base { - return element - } else { - return nil - } + let finalElements: [Element] = array.filter { element in + return element.expired || element.state.value != .base } let newAuthIds: [String] = self.map { $0.authorizationId } let newConnectionIds: [String] = self.map { $0.connectionId } var merged: [Element] = self - merged.append(contentsOf: finalElements - .filter { !newAuthIds.contains($0.authorizationId) || !newConnectionIds.contains($0.connectionId) } + merged.append( + contentsOf: finalElements.filter { + !newAuthIds.contains($0.authorizationId) || + !newConnectionIds.contains($0.connectionId) + } ) return merged diff --git a/Example/Authenticator/Utils/Extensions/SEEncryptedDataExtensions.swift b/Example/Authenticator/Utils/Extensions/SEEncryptedDataExtensions.swift index 8c1fbd3a..910380e1 100644 --- a/Example/Authenticator/Utils/Extensions/SEEncryptedDataExtensions.swift +++ b/Example/Authenticator/Utils/Extensions/SEEncryptedDataExtensions.swift @@ -22,9 +22,10 @@ import Foundation import SEAuthenticator +import SEAuthenticatorV2 import SEAuthenticatorCore - extension SEEncryptedData { +extension SEBaseEncryptedAuthorizationData { var decryptedAuthorizationData: SEAuthorizationData? { if let decryptedDictionary = self.decryptedDictionary { return SEAuthorizationData(decryptedDictionary) @@ -32,10 +33,23 @@ import SEAuthenticatorCore return nil } + var decryptedAuthorizationDataV2: SEAuthorizationDataV2? { + if let decryptedDictionary = self.decryptedDictionary, + let v2Response = self as? SEEncryptedAuthorizationData, + let connectionId = connectionId { + return SEAuthorizationDataV2( + decryptedDictionary, + id: v2Response.id, + connectionId: connectionId, + status: v2Response.status + ) + } + return nil + } + var decryptedConsentData: SEConsentData? { if let connectionId = self.connectionId, let decryptedDictionary = self.decryptedDictionary { - return SEConsentData(decryptedDictionary, connectionId) } return nil diff --git a/Example/Authenticator/Utils/Interactors/v1/AuthorizationsInteractor.swift b/Example/Authenticator/Utils/Interactors/v1/AuthorizationsInteractor.swift index fe0451d5..2744eff5 100644 --- a/Example/Authenticator/Utils/Interactors/v1/AuthorizationsInteractor.swift +++ b/Example/Authenticator/Utils/Interactors/v1/AuthorizationsInteractor.swift @@ -22,6 +22,7 @@ import Foundation import SEAuthenticator +import SEAuthenticatorV2 import SEAuthenticatorCore struct AuthorizationsInteractor { @@ -60,7 +61,7 @@ struct AuthorizationsInteractor { static func refresh( connection: Connection, authorizationId: ID, - success: @escaping (SEEncryptedData) -> (), + success: @escaping (SEBaseEncryptedAuthorizationData) -> (), failure: ((String) -> ())? = nil, connectionNotFoundFailure: @escaping ((String?) -> ()) ) { @@ -68,24 +69,46 @@ struct AuthorizationsInteractor { guard let baseUrl = connection.baseUrl else { failure?(l10n(.somethingWentWrong)); return } - SEAuthorizationManager.getEncryptedAuthorization( - data: SEBaseAuthenticatedWithIdRequestData( - url: baseUrl, - connectionGuid: connection.guid, - accessToken: accessToken, - appLanguage: UserDefaultsHelper.applicationLanguage, - entityId: authorizationId - ), - onSuccess: { response in - success(response.data) - }, - onFailure: { error in - if SEAPIError.connectionNotFound.isConnectionNotFound(error) { - connectionNotFoundFailure(connection.id) - } else { - failure?("\(l10n(.somethingWentWrong)) (\(connection.name))") + if connection.isApiV2 { + SEAuthorizationManagerV2.getEncryptedAuthorization( + data: SEBaseAuthenticatedWithIdRequestData( + url: baseUrl, + connectionGuid: connection.guid, + accessToken: accessToken, + appLanguage: UserDefaultsHelper.applicationLanguage, + entityId: authorizationId + ), + onSuccess: { response in + success(response.data) + }, + onFailure: { error in + if SEAPIError.connectionNotFound.isConnectionNotFound(error) { + connectionNotFoundFailure(connection.id) + } else { + failure?("\(l10n(.somethingWentWrong)) (\(connection.name))") + } } - } - ) + ) + } else { + SEAuthorizationManager.getEncryptedAuthorization( + data: SEBaseAuthenticatedWithIdRequestData( + url: baseUrl, + connectionGuid: connection.guid, + accessToken: accessToken, + appLanguage: UserDefaultsHelper.applicationLanguage, + entityId: authorizationId + ), + onSuccess: { response in + success(response.data) + }, + onFailure: { error in + if SEAPIError.connectionNotFound.isConnectionNotFound(error) { + connectionNotFoundFailure(connection.id) + } else { + failure?("\(l10n(.somethingWentWrong)) (\(connection.name))") + } + } + ) + } } } diff --git a/Example/Authenticator/Utils/Interactors/v1/CollectionsInteractor.swift b/Example/Authenticator/Utils/Interactors/v1/CollectionsInteractor.swift index 9fb10797..430de959 100644 --- a/Example/Authenticator/Utils/Interactors/v1/CollectionsInteractor.swift +++ b/Example/Authenticator/Utils/Interactors/v1/CollectionsInteractor.swift @@ -22,6 +22,7 @@ import Foundation import SEAuthenticator +import SEAuthenticatorV2 import SEAuthenticatorCore enum CollectionsInteractor { @@ -30,7 +31,7 @@ enum CollectionsInteractor { func refresh( connection: Connection, - success: @escaping ([SEEncryptedData]) -> (), + success: @escaping ([SEBaseEncryptedAuthorizationData]) -> (), failure: ((String) -> ())? = nil, connectionNotFoundFailure: @escaping ((String?) -> ()) ) { @@ -53,11 +54,19 @@ enum CollectionsInteractor { switch self { case .authorizations: - SEAuthorizationManager.getEncryptedAuthorizations( - data: requestData, - onSuccess: { response in success(response.data) }, - onFailure: { error in onFailure(error: error, connection: connection) } - ) + if connection.isApiV2 { + SEAuthorizationManagerV2.getEncryptedAuthorizations( + data: requestData, + onSuccess: { response in success(response.data) }, + onFailure: { error in onFailure(error: error, connection: connection) } + ) + } else { + SEAuthorizationManager.getEncryptedAuthorizations( + data: requestData, + onSuccess: { response in success(response.data) }, + onFailure: { error in onFailure(error: error, connection: connection) } + ) + } case .consents: SEConsentsManager.getEncryptedConsents( data: requestData, @@ -69,11 +78,11 @@ enum CollectionsInteractor { func refresh( connections: [Connection], - success: @escaping ([SEEncryptedData]) -> (), + success: @escaping ([SEBaseEncryptedAuthorizationData]) -> (), failure: ((String) -> ())? = nil, connectionNotFoundFailure: @escaping ((String?) -> ()) ) { - var encryptedAuthorizations = [SEEncryptedData]() + var encryptedAuthorizations = [SEBaseEncryptedAuthorizationData]() var numberOfResponses = 0 @@ -85,7 +94,7 @@ enum CollectionsInteractor { } } - func onSuccess(data: [SEEncryptedData]) { + func onSuccess(data: [SEBaseEncryptedAuthorizationData]) { encryptedAuthorizations.append(contentsOf: data) incrementAndCheckResponseCount() @@ -113,11 +122,19 @@ enum CollectionsInteractor { switch self { case .authorizations: - SEAuthorizationManager.getEncryptedAuthorizations( - data: requestData, - onSuccess: { response in onSuccess(data: response.data) }, - onFailure: { error in onFailure(error: error, connection: connection) } - ) + if connection.isApiV2 { + SEAuthorizationManagerV2.getEncryptedAuthorizations( + data: requestData, + onSuccess: { response in onSuccess(data: response.data) }, + onFailure: { error in onFailure(error: error, connection: connection) } + ) + } else { + SEAuthorizationManager.getEncryptedAuthorizations( + data: requestData, + onSuccess: { response in onSuccess(data: response.data) }, + onFailure: { error in onFailure(error: error, connection: connection) } + ) + } case .consents: SEConsentsManager.getEncryptedConsents( data: requestData, diff --git a/Example/Authenticator/View Models/Authorizations/AuthorizationDetailViewModel.swift b/Example/Authenticator/View Models/Authorizations/AuthorizationDetailViewModel.swift index 09f7ad0a..f8f54e3d 100644 --- a/Example/Authenticator/View Models/Authorizations/AuthorizationDetailViewModel.swift +++ b/Example/Authenticator/View Models/Authorizations/AuthorizationDetailViewModel.swift @@ -22,6 +22,8 @@ import Foundation import SEAuthenticator +import SEAuthenticatorV2 +import SEAuthenticatorCore protocol AuthorizationDetailEventsDelegate: class { func confirmPressed(_ authorizationId: String) @@ -44,15 +46,22 @@ final class AuthorizationDetailViewModel: Equatable { } var state = Observable(.base) var showLocationWarning: Bool + var apiVersion: ApiVersion weak var delegate: AuthorizationDetailEventsDelegate? - init?(_ data: SEAuthorizationData, showLocationWarning: Bool) { + init?(_ data: SEBaseAuthorizationData, apiVersion: ApiVersion, showLocationWarning: Bool) { + if let dataV1 = data as? SEAuthorizationData { + self.title = dataV1.title + self.description = dataV1.description + } else if let dataV2 = data as? SEAuthorizationDataV2 { + self.title = dataV2.title + self.description = "This is V2" // TODO: Fix description + } + self.apiVersion = apiVersion self.authorizationId = data.id self.connectionId = data.connectionId self.authorizationCode = data.authorizationCode - self.title = data.title - self.description = data.description self.authorizationExpiresAt = data.expiresAt self.lifetime = Int(data.expiresAt.timeIntervalSince(data.createdAt)) self.createdAt = data.createdAt diff --git a/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift b/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift index c558fa6d..e7c899dd 100644 --- a/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift +++ b/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift @@ -21,7 +21,7 @@ // import UIKit -import SEAuthenticator +import SEAuthenticatorCore enum AuthorizationsViewModelState: Equatable { case changedConnectionsData @@ -141,11 +141,15 @@ class AuthorizationsViewModel { ) } - private func updateDataSource(with authorizations: [SEAuthorizationData]) { + private func updateDataSource(with authorizations: [SEBaseAuthorizationData]) { if dataSource.update(with: authorizations) { state.value = .reloadData } + scrollToSingleAuthorization() + } + + private func scrollToSingleAuthorization() { if let authorizationToScroll = singleAuthorization { if let detailViewModel = dataSource.viewModel( by: authorizationToScroll.connectionId, @@ -256,10 +260,11 @@ private extension AuthorizationsViewModel { guard let strongSelf = self else { return } DispatchQueue.global(qos: .background).async { - let decryptedAuthorizations = encryptedAuthorizations.compactMap { $0.decryptedAuthorizationData } + let decryptedAuthorizationsV1 = encryptedAuthorizations.compactMap { $0.decryptedAuthorizationData } + let decryptedAuthorizationsV2 = encryptedAuthorizations.compactMap { $0.decryptedAuthorizationDataV2 } DispatchQueue.main.async { - strongSelf.updateDataSource(with: decryptedAuthorizations) + strongSelf.updateDataSource(with: decryptedAuthorizationsV1 + decryptedAuthorizationsV2) } } }, diff --git a/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift b/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift index 1a2b3301..4c67f545 100644 --- a/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift +++ b/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift @@ -58,6 +58,7 @@ final class SingleAuthorizationViewModel { DispatchQueue.main.async { if let viewModel = AuthorizationDetailViewModel( decryptedAuthorizationData, + apiVersion: "1", showLocationWarning: showLocationWarning ) { strongSelf.detailViewModel = viewModel diff --git a/SaltedgeAuthenticatorCore/Classes/API/Models/SEBaseAuthorizationData.swift b/SaltedgeAuthenticatorCore/Classes/API/Models/SEBaseAuthorizationData.swift new file mode 100644 index 00000000..544d6be4 --- /dev/null +++ b/SaltedgeAuthenticatorCore/Classes/API/Models/SEBaseAuthorizationData.swift @@ -0,0 +1,32 @@ +// +// SEBaseAuthorizationData +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation + +public protocol SEBaseAuthorizationData { + var id: String { get } + var connectionId: String { get } + var createdAt: Date { get } + var expiresAt: Date { get } + var authorizationCode: String? { get } + var apiVersion: ApiVersion { get } +} diff --git a/SaltedgeAuthenticatorCore/Classes/API/Models/SEBaseEncryptedAuthorizationData.swift b/SaltedgeAuthenticatorCore/Classes/API/Models/SEBaseEncryptedAuthorizationData.swift new file mode 100644 index 00000000..48692b2f --- /dev/null +++ b/SaltedgeAuthenticatorCore/Classes/API/Models/SEBaseEncryptedAuthorizationData.swift @@ -0,0 +1,30 @@ +// +// SEBaseEncryptedAuthorizationData +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation + +public protocol SEBaseEncryptedAuthorizationData { + var data: String { get } + var key: String { get } + var iv: String { get } + var connectionId: String? { get } +} diff --git a/SaltedgeAuthenticatorCore/Classes/API/SEEncryptedData.swift b/SaltedgeAuthenticatorCore/Classes/API/SEEncryptedData.swift index 1099f861..69bb1a39 100644 --- a/SaltedgeAuthenticatorCore/Classes/API/SEEncryptedData.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/SEEncryptedData.swift @@ -22,7 +22,7 @@ import Foundation -public struct SEEncryptedData: SerializableResponse, Equatable { +public struct SEEncryptedData: SEBaseEncryptedAuthorizationData, SerializableResponse, Equatable { private let defaultAlgorithm = "AES-256-CBC" public let data: String diff --git a/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelper.swift b/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelper.swift index 1d39c7b3..26e585ae 100644 --- a/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelper.swift +++ b/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelper.swift @@ -100,13 +100,9 @@ public struct SECryptoHelper { ) } - public static func decrypt(_ encryptedData: SEEncryptedData, tag: KeyTag) throws -> String { + public static func decrypt(_ encryptedData: SEBaseEncryptedAuthorizationData, tag: KeyTag) throws -> String { let privateKey = try SecKeyHelper.obtainKey(for: tag.privateTag) - return try decrypt(encryptedData, privateKey: privateKey) - } - - public static func decrypt(_ encryptedData: SEEncryptedData, privateKey: SecKey) throws -> String { let decryptedKey = try privateDecrypt(message: encryptedData.key, privateKey: privateKey) let decryptedIv = try privateDecrypt(message: encryptedData.iv, privateKey: privateKey) diff --git a/SaltedgeAuthenticatorSDK/Classes/Helpers/DateUtils.swift b/SaltedgeAuthenticatorCore/Classes/Helpers/DateUtils.swift similarity index 96% rename from SaltedgeAuthenticatorSDK/Classes/Helpers/DateUtils.swift rename to SaltedgeAuthenticatorCore/Classes/Helpers/DateUtils.swift index a1d50bd1..543583a3 100644 --- a/SaltedgeAuthenticatorSDK/Classes/Helpers/DateUtils.swift +++ b/SaltedgeAuthenticatorCore/Classes/Helpers/DateUtils.swift @@ -1,8 +1,8 @@ // -// DateUtils.swift +// DateUtils // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by diff --git a/SaltedgeAuthenticatorSDK/Classes/Helpers/SEPollingTimer.swift b/SaltedgeAuthenticatorCore/Classes/Helpers/SEPollingTimer.swift similarity index 96% rename from SaltedgeAuthenticatorSDK/Classes/Helpers/SEPollingTimer.swift rename to SaltedgeAuthenticatorCore/Classes/Helpers/SEPollingTimer.swift index eb9fe1b7..152c20fa 100644 --- a/SaltedgeAuthenticatorSDK/Classes/Helpers/SEPollingTimer.swift +++ b/SaltedgeAuthenticatorCore/Classes/Helpers/SEPollingTimer.swift @@ -1,8 +1,8 @@ // -// SEPoller +// SEPollingTimer // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2020 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -47,3 +47,4 @@ public class SEPoller { timer = nil } } + diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/SEAuthorizationData.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/SEAuthorizationData.swift index d4f194e2..a26e7ed6 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/SEAuthorizationData.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/SEAuthorizationData.swift @@ -23,14 +23,15 @@ import Foundation import SEAuthenticatorCore -public struct SEAuthorizationData { - public let id: String +public class SEAuthorizationData: SEBaseAuthorizationData { + public var id: String public let connectionId: String public let title: String public let description: String - public let createdAt: Date - public let expiresAt: Date + public var createdAt: Date + public var expiresAt: Date public var authorizationCode: String? + public var apiVersion: ApiVersion = "1" public init?(_ dictionary: [String: Any]) { if let id = dictionary[SENetKeys.id] as? String, diff --git a/SaltedgeAuthenticatorSDK/Classes/Helpers/SignatureHelper.swift b/SaltedgeAuthenticatorSDK/Classes/Helpers/SignatureHelper.swift index 0cef0360..f69a1ff1 100644 --- a/SaltedgeAuthenticatorSDK/Classes/Helpers/SignatureHelper.swift +++ b/SaltedgeAuthenticatorSDK/Classes/Helpers/SignatureHelper.swift @@ -25,11 +25,13 @@ import CommonCrypto import SEAuthenticatorCore struct SignatureHelper { - static func signedPayload(method: HTTPMethod, - urlString: String, - guid: GUID, - expiresAt: Int, - params: [String: Any]?) -> String? { + static func signedPayload( + method: HTTPMethod, + urlString: String, + guid: GUID, + expiresAt: Int, + params: [String: Any]? + ) -> String? { let bodyString: String if let payloadParams = params, let payloadBody = ParametersSerializer.createBody(parameters: payloadParams) { bodyString = String(data: payloadBody, encoding: .utf8) ?? "" diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEAuthorizationManager.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEAuthorizationManagerV2.swift similarity index 94% rename from SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEAuthorizationManager.swift rename to SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEAuthorizationManagerV2.swift index e9dbb296..29f05a6c 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEAuthorizationManager.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEAuthorizationManagerV2.swift @@ -23,15 +23,14 @@ import Foundation import SEAuthenticatorCore -public struct SEAuthorizationManager { +public struct SEAuthorizationManagerV2 { public static func getEncryptedAuthorizations( - url: URL, - accessToken: AccessToken, + data: SEBaseAuthenticatedRequestData, onSuccess success: @escaping HTTPServiceSuccessClosure, onFailure failure: @escaping FailureBlock ) { HTTPService.execute( - request: SEAuthorizationRouter.list(url, accessToken), + request: SEAuthorizationRouter.list(data), success: success, failure: failure ) diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEAuthorizationDataV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEAuthorizationDataV2.swift new file mode 100644 index 00000000..d1d2040f --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEAuthorizationDataV2.swift @@ -0,0 +1,71 @@ +// +// SEAuthorizationDataV2 +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +public class SEAuthorizationDataV2: SEBaseAuthorizationData { + public let title: String + public let description: [String: Any] + public var createdAt: Date + public var expiresAt: Date + public var authorizationCode: String? + + public var id: String + public var connectionId: String + public var status: String + + public var apiVersion: ApiVersion = "2" + + public init?(_ dictionary: [String: Any], id: String, connectionId: String, status: String) { + if let title = dictionary[SENetKeys.title] as? String, + let description = dictionary[SENetKeys.description] as? [String: Any], + let createdAt = (dictionary[SENetKeys.createdAt] as? String)?.iso8601date, + let expiresAt = (dictionary[SENetKeys.expiresAt] as? String)?.iso8601date, + let authorizationCode = dictionary[SENetKeys.authorizationCode] as? String { + self.authorizationCode = authorizationCode + self.title = title + self.description = description + self.createdAt = createdAt + self.expiresAt = expiresAt + self.id = id + self.connectionId = connectionId + self.status = status + } else { + return nil + } + } +} + +extension SEAuthorizationDataV2: Equatable { + public static func == (lhs: SEAuthorizationDataV2, rhs: SEAuthorizationDataV2) -> Bool { + return lhs.title == rhs.title && + lhs.description == rhs.description && + lhs.createdAt == rhs.createdAt && + lhs.expiresAt == rhs.expiresAt && + lhs.authorizationCode == rhs.authorizationCode + } +} + +private func ==(lhs: [String: Any], rhs: [String: Any] ) -> Bool { + return NSDictionary(dictionary: lhs).isEqual(to: rhs) +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEAuthorizationRouter.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEAuthorizationRouter.swift index 59acdedf..27b8e39e 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEAuthorizationRouter.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEAuthorizationRouter.swift @@ -24,7 +24,7 @@ import Foundation import SEAuthenticatorCore enum SEAuthorizationRouter: Routable { - case list(URL, AccessToken) + case list(SEBaseAuthenticatedRequestData) case show(SEBaseAuthenticatedWithIdRequestData) case confirm(URL, AccessToken, SEAuthorizationRequestData) case deny(URL, AccessToken, SEAuthorizationRequestData) @@ -45,8 +45,8 @@ enum SEAuthorizationRouter: Routable { var url: URL { switch self { - case .list(let url, _): - return url.appendingPathComponent( + case .list(let data): + return data.url.appendingPathComponent( "\(SENetPathBuilder(for: .authorizations, version: 2).path)" ) case .show(let data): @@ -66,8 +66,8 @@ enum SEAuthorizationRouter: Routable { var headers: [String : String]? { switch self { - case .list(_, let accessToken): - return Headers.authorizedRequestHeaders(token: accessToken) + case .list(let data): + return Headers.authorizedRequestHeaders(token: data.accessToken) case .show(let data): return Headers.authorizedRequestHeaders(token: data.accessToken) case .confirm(_, let accessToken, let data), .deny(_, let accessToken, let data): diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/SEEncryptedAuthorizationData.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/SEEncryptedAuthorizationData.swift index dfd6d8d5..0cf9344c 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/SEEncryptedAuthorizationData.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/SEEncryptedAuthorizationData.swift @@ -23,28 +23,28 @@ import Foundation import SEAuthenticatorCore -public struct SEEncryptedAuthorizationData: SerializableResponse { +public struct SEEncryptedAuthorizationData: SEBaseEncryptedAuthorizationData, SerializableResponse { public let id: String public let data: String public let key: String public let iv: String public let status: String - public var connectionId: String + public var connectionId: String? public init?(_ value: Any) { if let dict = value as? [String: Any], - let id = dict[SENetKeys.id] as? String, + let id = dict[SENetKeys.id] as? Int, let data = dict[SENetKeys.data] as? String, let key = dict[SENetKeys.key] as? String, let iv = dict[SENetKeys.iv] as? String, let status = dict[SENetKeys.status] as? String, - let connectionId = dict[SENetKeys.connectionId] as? String { - self.id = id + let connectionId = dict[SENetKeys.connectionId] as? Int { + self.id = "\(id)" self.data = data self.key = key self.iv = iv self.status = status - self.connectionId = connectionId + self.connectionId = "\(connectionId)" } else { return nil } From 115d1c7affd6b412158e6e2d778009d7c794dda1 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Fri, 4 Jun 2021 16:11:07 +0300 Subject: [PATCH 021/110] fixed specs, removed geolocation from auth data source --- .../AuthorizationsDataSource.swift | 49 +++++------- .../AuthorizationDetailViewModel.swift | 15 ++-- .../AuthorizationsViewModel.swift | 16 ++-- .../SingleAuthorizationViewModel.swift | 11 +-- .../AuthorizationContentView.swift | 14 +--- .../Authorizations/AuthorizationView.swift | 9 ++- Example/Tests/Helpers/DateUtilsSpec.swift | 2 +- .../AuthorizationsDataSourceSpec.swift | 75 ++++++++++++------- Example/Tests/Spec Helpers/SpecUtils.swift | 24 +++++- .../AuthorizationsViewModelSpec.swift | 8 +- .../Routers/SEAuthorizationRouterSpecV2.swift | 9 ++- .../Extensions/DictionaryExtensions.swift | 47 ------------ 12 files changed, 129 insertions(+), 150 deletions(-) delete mode 100644 SaltedgeAuthenticatorSDK/Classes/Extensions/DictionaryExtensions.swift diff --git a/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift b/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift index 3d1b8bc3..262147c7 100644 --- a/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift +++ b/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift @@ -29,41 +29,22 @@ final class AuthorizationsDataSource { private var viewModels = [AuthorizationDetailViewModel]() private var locationManagement: LocationManagement + // TODO: Fix geolocation according to new design init(locationManagement: LocationManagement) { self.locationManagement = locationManagement } func update(with baseData: [SEBaseAuthorizationData]) -> Bool { - let viewModelsV1 = baseData.filter { $0.apiVersion == "1" }.compactMap { response in - guard response.expiresAt >= Date() else { return nil } - - let connection = ConnectionsCollector.with(id: response.connectionId) - let showLocationWarning = locationManagement.showLocationWarning(connection: connection) - - return AuthorizationDetailViewModel( - response, - apiVersion: response.apiVersion, - showLocationWarning: showLocationWarning - ) - }.merge(array: self.viewModels.filter { $0.apiVersion == "1" }) - - let viewModelsV2 = baseData.filter { $0.apiVersion == "2" }.compactMap { response in - guard response.expiresAt >= Date() else { return nil } - - let connection = ConnectionsCollector.with(id: response.connectionId) - let showLocationWarning = locationManagement.showLocationWarning(connection: connection) + let viewModelsV1 = baseData.toAuthorizationViewModel(apiVersion: "1") + .merge(array: self.viewModels.filter { $0.apiVersion == "1" }) - return AuthorizationDetailViewModel( - response, - apiVersion: response.apiVersion, - showLocationWarning: showLocationWarning - ) - }.merge(array: self.viewModels.filter { $0.apiVersion == "2" }) + let viewModelsV2 = baseData.toAuthorizationViewModel(apiVersion: "2") + .merge(array: self.viewModels.filter { $0.apiVersion == "2" }) let allViewModels = viewModelsV1 + viewModelsV2 if allViewModels != self.viewModels { - self.viewModels = (viewModelsV1 + viewModelsV2).sorted(by: { $0.createdAt < $1.createdAt }) + self.viewModels = allViewModels.sorted(by: { $0.createdAt < $1.createdAt }) return true } @@ -106,12 +87,12 @@ final class AuthorizationsDataSource { return viewModels.firstIndex(of: viewModel) } - func viewModel(with authorizationId: String) -> AuthorizationDetailViewModel? { - return viewModels.filter({ $0.authorizationId == authorizationId }).first + func viewModel(with authorizationId: String, apiVersion: ApiVersion) -> AuthorizationDetailViewModel? { + return viewModels.filter { $0.authorizationId == authorizationId && $0.apiVersion == apiVersion}.first } - func confirmationData(for authorizationId: String) -> SEConfirmAuthorizationRequestData? { - guard let viewModel = viewModel(with: authorizationId), + func confirmationData(for authorizationId: String, apiVersion: ApiVersion) -> SEConfirmAuthorizationRequestData? { + guard let viewModel = viewModel(with: authorizationId, apiVersion: apiVersion), let connection = ConnectionsCollector.with(id: viewModel.connectionId), let url = connection.baseUrl else { return nil } @@ -148,6 +129,16 @@ final class AuthorizationsDataSource { } } +private extension Array where Element == SEBaseAuthorizationData { + func toAuthorizationViewModel(apiVersion: ApiVersion) -> [AuthorizationDetailViewModel] { + return filter { $0.apiVersion == apiVersion }.compactMap { response in + guard response.expiresAt >= Date() else { return nil } + + return AuthorizationDetailViewModel(response, apiVersion: response.apiVersion) + } + } +} + private extension Array where Element == AuthorizationDetailViewModel { func merge(array: [Element]) -> [AuthorizationDetailViewModel] { let finalElements: [Element] = array.filter { element in diff --git a/Example/Authenticator/View Models/Authorizations/AuthorizationDetailViewModel.swift b/Example/Authenticator/View Models/Authorizations/AuthorizationDetailViewModel.swift index f8f54e3d..6643b47c 100644 --- a/Example/Authenticator/View Models/Authorizations/AuthorizationDetailViewModel.swift +++ b/Example/Authenticator/View Models/Authorizations/AuthorizationDetailViewModel.swift @@ -26,8 +26,8 @@ import SEAuthenticatorV2 import SEAuthenticatorCore protocol AuthorizationDetailEventsDelegate: class { - func confirmPressed(_ authorizationId: String) - func denyPressed(_ authorizationId: String) + func confirmPressed(_ authorizationId: String, apiVersion: ApiVersion) + func denyPressed(_ authorizationId: String, apiVersion: ApiVersion) func authorizationExpired() } @@ -45,12 +45,11 @@ final class AuthorizationDetailViewModel: Equatable { authorizationExpiresAt < Date() } var state = Observable(.base) - var showLocationWarning: Bool var apiVersion: ApiVersion weak var delegate: AuthorizationDetailEventsDelegate? - init?(_ data: SEBaseAuthorizationData, apiVersion: ApiVersion, showLocationWarning: Bool) { + init?(_ data: SEBaseAuthorizationData, apiVersion: ApiVersion) { if let dataV1 = data as? SEAuthorizationData { self.title = dataV1.title self.description = dataV1.description @@ -66,7 +65,6 @@ final class AuthorizationDetailViewModel: Equatable { self.lifetime = Int(data.expiresAt.timeIntervalSince(data.createdAt)) self.createdAt = data.createdAt self.state.value = data.expiresAt < Date() ? .expired : .base - self.showLocationWarning = showLocationWarning } static func == (lhs: AuthorizationDetailViewModel, rhs: AuthorizationDetailViewModel) -> Bool { @@ -74,7 +72,8 @@ final class AuthorizationDetailViewModel: Equatable { lhs.connectionId == rhs.connectionId && lhs.title == rhs.title && lhs.description == rhs.description && - lhs.createdAt == rhs.createdAt + lhs.createdAt == rhs.createdAt && + lhs.apiVersion == rhs.apiVersion } var authorizationExpired: Bool = false { @@ -86,10 +85,10 @@ final class AuthorizationDetailViewModel: Equatable { } func confirmPressed() { - delegate?.confirmPressed(authorizationId) + delegate?.confirmPressed(authorizationId, apiVersion: apiVersion) } func denyPressed() { - delegate?.denyPressed(authorizationId) + delegate?.denyPressed(authorizationId, apiVersion: apiVersion) } } diff --git a/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift b/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift index e7c899dd..6f9aec71 100644 --- a/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift +++ b/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift @@ -53,11 +53,9 @@ class AuthorizationsViewModel { var dataSource: AuthorizationsDataSource! private var connectionsListener: RealmConnectionsListener? - private var poller: SEPoller? + private var poller: SEPoller? // TODO: Think about moving poller to interactor private var connections = ConnectionsCollector.activeConnections - var singleAuthorizationDetailViewModel: AuthorizationDetailViewModel? - var singleAuthorization: (connectionId: String, authorizationId: String)? { willSet { setupPolling() @@ -103,9 +101,9 @@ class AuthorizationsViewModel { } } - func confirmAuthorization(by authorizationId: String) { - guard let data = dataSource.confirmationData(for: authorizationId), - let detailViewModel = dataSource.viewModel(with: authorizationId) else { return } + func confirmAuthorization(by authorizationId: String, apiVersion: ApiVersion) { + guard let data = dataSource.confirmationData(for: authorizationId, apiVersion: apiVersion), + let detailViewModel = dataSource.viewModel(with: authorizationId, apiVersion: apiVersion) else { return } detailViewModel.state.value = .processing @@ -122,9 +120,9 @@ class AuthorizationsViewModel { ) } - func denyAuthorization(by authorizationId: String) { - guard let data = dataSource.confirmationData(for: authorizationId), - let detailViewModel = dataSource.viewModel(with: authorizationId) else { return } + func denyAuthorization(by authorizationId: String, apiVersion: ApiVersion) { + guard let data = dataSource.confirmationData(for: authorizationId, apiVersion: apiVersion), + let detailViewModel = dataSource.viewModel(with: authorizationId, apiVersion: apiVersion) else { return } detailViewModel.state.value = .processing diff --git a/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift b/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift index 4c67f545..03389dc2 100644 --- a/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift +++ b/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift @@ -22,6 +22,7 @@ import Foundation import SEAuthenticator +import SEAuthenticatorCore protocol SingleAuthorizationViewModelEventsDelegate: class { func receivedDetailViewModel(_ detailViewModel: AuthorizationDetailViewModel) @@ -56,11 +57,7 @@ final class SingleAuthorizationViewModel { guard let decryptedAuthorizationData = encryptedAuthorization.decryptedAuthorizationData else { return } DispatchQueue.main.async { - if let viewModel = AuthorizationDetailViewModel( - decryptedAuthorizationData, - apiVersion: "1", - showLocationWarning: showLocationWarning - ) { + if let viewModel = AuthorizationDetailViewModel(decryptedAuthorizationData, apiVersion: "1") { strongSelf.detailViewModel = viewModel strongSelf.detailViewModel?.delegate = self @@ -83,7 +80,7 @@ final class SingleAuthorizationViewModel { // MARK: - AuthorizationDetailEventsDelegate extension SingleAuthorizationViewModel: AuthorizationDetailEventsDelegate { - func confirmPressed(_ authorizationId: String) { + func confirmPressed(_ authorizationId: String, apiVersion: ApiVersion) { guard let detailViewModel = detailViewModel, let connection = connection, let url = connection.baseUrl else { return } let confirmData = SEConfirmAuthorizationRequestData( @@ -118,7 +115,7 @@ extension SingleAuthorizationViewModel: AuthorizationDetailEventsDelegate { ) } - func denyPressed(_ authorizationId: String) { + func denyPressed(_ authorizationId: String, apiVersion: ApiVersion) { guard let detailViewModel = detailViewModel, let connection = connection, let url = connection.baseUrl else { return } let confirmData = SEConfirmAuthorizationRequestData( diff --git a/Example/Authenticator/Views/Authorizations/AuthorizationContentView.swift b/Example/Authenticator/Views/Authorizations/AuthorizationContentView.swift index b8454fc9..24c0c131 100644 --- a/Example/Authenticator/Views/Authorizations/AuthorizationContentView.swift +++ b/Example/Authenticator/Views/Authorizations/AuthorizationContentView.swift @@ -56,18 +56,11 @@ final class AuthorizationContentView: UIView { stackView.spacing = 11.0 return stackView }() - private let locationWarningLabel = UILabel(font: .systemFont(ofSize: 18.0, weight: .regular), textColor: .redAlert) var viewModel: AuthorizationDetailViewModel! { didSet { titleLabel.text = viewModel.title - if viewModel.showLocationWarning { - locationWarningLabel.text = l10n(.locationWarning) - } - buttonsStackView.isHidden = viewModel.showLocationWarning - locationWarningLabel.isHidden = !viewModel.showLocationWarning - guard viewModel.state.value == .base else { stateView.set(state: viewModel.state.value) return @@ -140,7 +133,7 @@ private extension AuthorizationContentView { // MARK: - Layout extension AuthorizationContentView: Layoutable { func layout() { - addSubviews(titleLabel, contentStackView, buttonsStackView, locationWarningLabel, stateView) + addSubviews(titleLabel, contentStackView, buttonsStackView, stateView) titleLabel.top(to: self, offset: Layout.titleLabelTopOffset) titleLabel.centerX(to: self) @@ -156,11 +149,6 @@ extension AuthorizationContentView: Layoutable { buttonsStackView.bottom(to: self, safeAreaLayoutGuide.bottomAnchor, offset: -Layout.bottomOffset) buttonsStackView.centerXToSuperview() - locationWarningLabel.leftToSuperview(offset: Layout.sideOffset) - locationWarningLabel.rightToSuperview(offset: -Layout.sideOffset) - locationWarningLabel.bottom(to: self, safeAreaLayoutGuide.bottomAnchor, offset: -Layout.bottomOffset) - locationWarningLabel.centerXToSuperview() - stateView.edgesToSuperview() } } diff --git a/Example/Authenticator/Views/Authorizations/AuthorizationView.swift b/Example/Authenticator/Views/Authorizations/AuthorizationView.swift index d0603fb1..c78f85d1 100644 --- a/Example/Authenticator/Views/Authorizations/AuthorizationView.swift +++ b/Example/Authenticator/Views/Authorizations/AuthorizationView.swift @@ -21,6 +21,7 @@ // import UIKit +import SEAuthenticatorCore private struct Layout { static let headerSpacing: CGFloat = 16.0 @@ -192,12 +193,12 @@ extension AuthorizationView: UICollectionViewDelegate, UICollectionViewDelegateF // MARK: - AuthorizationCellDelegate extension AuthorizationView: AuthorizationDetailEventsDelegate { - func confirmPressed(_ authorizationId: String) { - viewModel.confirmAuthorization(by: authorizationId) + func confirmPressed(_ authorizationId: String, apiVersion: ApiVersion) { + viewModel.confirmAuthorization(by: authorizationId, apiVersion: apiVersion) } - func denyPressed(_ authorizationId: String) { - viewModel.denyAuthorization(by: authorizationId) + func denyPressed(_ authorizationId: String, apiVersion: ApiVersion) { + viewModel.denyAuthorization(by: authorizationId, apiVersion: apiVersion) } func authorizationExpired() {} diff --git a/Example/Tests/Helpers/DateUtilsSpec.swift b/Example/Tests/Helpers/DateUtilsSpec.swift index 12002b26..2ac07a39 100644 --- a/Example/Tests/Helpers/DateUtilsSpec.swift +++ b/Example/Tests/Helpers/DateUtilsSpec.swift @@ -22,7 +22,7 @@ import Quick import Nimble -@testable import SEAuthenticator +@testable import SEAuthenticatorCore class DateUtilsSpec: BaseSpec { override func spec() { diff --git a/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift b/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift index 90753268..c82a2142 100644 --- a/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift +++ b/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift @@ -51,8 +51,8 @@ class AuthorizationsDataSourceSpec: BaseSpec { let firstDecryptedData = SpecUtils.createAuthResponse(with: authMessage, id: connection.id, guid: connection.guid) let secondDecryptedData = SpecUtils.createAuthResponse(with: secondAuthMessage, id: connection.id, guid: connection.guid) - firstModel = AuthorizationDetailViewModel(firstDecryptedData, showLocationWarning: true) - secondModel = AuthorizationDetailViewModel(secondDecryptedData, showLocationWarning: false) + firstModel = AuthorizationDetailViewModel(firstDecryptedData, apiVersion: "1") + secondModel = AuthorizationDetailViewModel(secondDecryptedData, apiVersion: "1") _ = dataSource.update(with: [firstDecryptedData, secondDecryptedData]) } @@ -113,7 +113,7 @@ class AuthorizationsDataSourceSpec: BaseSpec { "created_at": Date().iso8601string, "expires_at": Date().addingTimeInterval(-3.0 * 60.0).iso8601string] let expiredDecryptedData = SpecUtils.createAuthResponse(with: expiredAuthMessage, id: connection.id, guid: connection.guid) - + let validAuthMessage = ["id": validAuthId, "connection_id": connection.id, "title": "Valid Authorization", @@ -126,19 +126,49 @@ class AuthorizationsDataSourceSpec: BaseSpec { _ = dataSource.update(with: [expiredDecryptedData, validDecryptedData]) expect(dataSource.rows).to(equal(1)) - expect(dataSource.viewModel(with: validAuthId)) - .to(equal(AuthorizationDetailViewModel(validDecryptedData, showLocationWarning: true))) - expect(dataSource.viewModel(with: validAuthId)!.showLocationWarning).to(beTrue()) - expect(dataSource.viewModel(with: expiredAuthId)).to(beNil()) + expect(dataSource.viewModel(with: validAuthId, apiVersion: "1")) + .to(equal(AuthorizationDetailViewModel(validDecryptedData,apiVersion: "1"))) - mockLocationManager.showLocationWarning = false + expect(dataSource.viewModel(with: expiredAuthId, apiVersion: "1")).to(beNil()) _ = dataSource.update(with: [validDecryptedData]) expect(dataSource.rows).to(equal(1)) - expect(dataSource.viewModel(with: validAuthId)) - .to(equal(AuthorizationDetailViewModel(validDecryptedData, showLocationWarning: false))) - expect(dataSource.viewModel(with: validAuthId)!.showLocationWarning).to(beFalse()) + expect(dataSource.viewModel(with: validAuthId, apiVersion: "1")) + .to(equal(AuthorizationDetailViewModel(validDecryptedData, apiVersion: "1"))) + } + } + + context("when new v2 authorizations were added") { + it("should return both v1 and v2 authorizations") { + // new authorization v1 + let validAuthMessage = ["id": "1111", + "connection_id": connection.id, + "title": "Valid Authorization", + "description": "Test valid authorization", + "created_at": Date().iso8601string, + "expires_at": Date().addingTimeInterval(3.0 * 60.0).iso8601string] + let validDecryptedDataV1 = SpecUtils.createAuthResponse(with: validAuthMessage, id: connection.id, guid: connection.guid) + + let connectionV2 = SpecUtils.createConnection(id: "999", apiVersion: "2") + let authorizationV2id = 30000 + + // new authorization v2 + let authMessage: [String: Any] = ["title": "Authorization V2", + "authorization_code": "code", + "description": ["text": "Test valid authorization"], + "created_at": Date().iso8601string, + "expires_at": Date().addingTimeInterval(3.0 * 60.0).iso8601string] + let validDecryptedDataV2 = SpecUtils.createAuthResponseV2( + with: authMessage, + authorizationId: authorizationV2id, + connectionId: Int(connectionV2.id)!, + guid: connectionV2.guid + ) + + _ = dataSource.update(with: [validDecryptedDataV1, validDecryptedDataV2]) + + expect(dataSource.rows).to(equal(2)) } } @@ -146,7 +176,7 @@ class AuthorizationsDataSourceSpec: BaseSpec { context("when one authorization expired and other was added") { it("should keep the expired one and add the new one") { expect(dataSource.rows).to(equal(2)) - + let authMessage = ["id": "909", "connection_id": connection.id, "title": "Expired Authorization", @@ -154,12 +184,12 @@ class AuthorizationsDataSourceSpec: BaseSpec { "created_at": Date().iso8601string, "expires_at": Date().addingTimeInterval(1.0).iso8601string] let decryptedData = SpecUtils.createAuthResponse(with: authMessage, id: connection.id, guid: connection.guid) - + _ = dataSource.update(with: [decryptedData]) sleep(1) - + expect(dataSource.rows).to(equal(1)) - + let newAuthMessage = ["id": "910", "connection_id": connection.id, "title": "Expired Authorization", @@ -167,9 +197,9 @@ class AuthorizationsDataSourceSpec: BaseSpec { "created_at": Date().iso8601string, "expires_at": Date().addingTimeInterval(3.0 * 60.0).iso8601string] let newDecryptedData = SpecUtils.createAuthResponse(with: newAuthMessage, id: connection.id, guid: connection.guid) - + _ = dataSource.update(with: [newDecryptedData]) - + expect(dataSource.rows).to(equal(2)) } } @@ -220,7 +250,7 @@ class AuthorizationsDataSourceSpec: BaseSpec { describe("viewModel(by:)") { context("when one of existed viewModels has connectionId and authorizationId equal for given params") { - it("should return existed viewModel") { + it("should return existed viewModel") { expect(dataSource.viewModel(by: "12345", authorizationId: "00000")).to(equal(firstModel)) } } @@ -249,16 +279,9 @@ class AuthorizationsDataSourceSpec: BaseSpec { "expires_at": Date().addingTimeInterval(5.0 * 60.0).iso8601string] let decryptedData = SEAuthorizationData(secondAuthMessage)! - expect(dataSource.index(of: AuthorizationDetailViewModel(decryptedData, showLocationWarning: true)!)).to(beNil()) + expect(dataSource.index(of: AuthorizationDetailViewModel(decryptedData, apiVersion: "1")!)).to(beNil()) } } } - - describe("item(for)") { - it("should return view model for given index") { - expect(dataSource.viewModel(at: 0)).to(equal(firstModel)) - expect(dataSource.viewModel(at: 1)).to(equal(secondModel)) - } - } } } diff --git a/Example/Tests/Spec Helpers/SpecUtils.swift b/Example/Tests/Spec Helpers/SpecUtils.swift index 4e5320e5..284c9396 100644 --- a/Example/Tests/Spec Helpers/SpecUtils.swift +++ b/Example/Tests/Spec Helpers/SpecUtils.swift @@ -22,13 +22,15 @@ import Foundation import SEAuthenticator +import SEAuthenticatorV2 import SEAuthenticatorCore struct SpecUtils { - static func createConnection(id: ID) -> Connection { + static func createConnection(id: ID, apiVersion: ApiVersion = "1") -> Connection { let connection = Connection() connection.id = id connection.baseUrlString = "url.com" + connection.apiVersion = apiVersion ConnectionRepository.save(connection) _ = SECryptoHelper.createKeyPair(with: SETagHelper.create(for: connection.guid)) @@ -58,6 +60,26 @@ struct SpecUtils { return SEEncryptedData(dict)!.decryptedAuthorizationData! } + static func createAuthResponseV2( + with authMessage: [String: Any], + authorizationId: Int, + connectionId: Int, + guid: GUID + ) -> SEAuthorizationDataV2 { + let encryptedData = try! SECryptoHelper.encrypt(authMessage.jsonString!, tag: SETagHelper.create(for: guid)) + + let dict: [String: Any] = [ + "data": encryptedData.data, + "key": encryptedData.key, + "iv": encryptedData.iv, + "id": authorizationId, + "connection_id": connectionId, + "status": "pending" + ] + + return SEEncryptedAuthorizationData(dict)!.decryptedAuthorizationDataV2! + } + public static var publicKeyPem: String { "-----BEGIN PUBLIC KEY-----\n" + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAppVU/nZZVewUCRVLz51X\n" + diff --git a/Example/Tests/ViewModels/AuthorizationsViewModelSpec.swift b/Example/Tests/ViewModels/AuthorizationsViewModelSpec.swift index ccf0a43e..467676a5 100644 --- a/Example/Tests/ViewModels/AuthorizationsViewModelSpec.swift +++ b/Example/Tests/ViewModels/AuthorizationsViewModelSpec.swift @@ -101,9 +101,9 @@ final class AuthorizationsViewModelSpec: BaseSpec { _ = dataSource.update(with: [decryptedData]) - let detailViewModel = dataSource.viewModel(with: "00000") + let detailViewModel = dataSource.viewModel(with: "00000", apiVersion: "1") - viewModel.confirmAuthorization(by: "00000") + viewModel.confirmAuthorization(by: "00000", apiVersion: "1") expect(detailViewModel?.state.value).to(equal(AuthorizationStateView.AuthorizationState.processing)) expect(detailViewModel?.state.value).toEventually(equal(AuthorizationStateView.AuthorizationState.undefined)) @@ -129,9 +129,9 @@ final class AuthorizationsViewModelSpec: BaseSpec { _ = dataSource.update(with: [decryptedData]) - let detailViewModel = dataSource.viewModel(with: "00000") + let detailViewModel = dataSource.viewModel(with: "00000", apiVersion: "1") - viewModel.denyAuthorization(by: "00000") + viewModel.denyAuthorization(by: "00000", apiVersion: "1") expect(detailViewModel?.state.value).to(equal(AuthorizationStateView.AuthorizationState.processing)) expect(detailViewModel?.state.value).toEventually(equal(AuthorizationStateView.AuthorizationState.undefined)) diff --git a/Example/Tests/v2/Networking/Routers/SEAuthorizationRouterSpecV2.swift b/Example/Tests/v2/Networking/Routers/SEAuthorizationRouterSpecV2.swift index bc9b6c64..d92ae162 100644 --- a/Example/Tests/v2/Networking/Routers/SEAuthorizationRouterSpecV2.swift +++ b/Example/Tests/v2/Networking/Routers/SEAuthorizationRouterSpecV2.swift @@ -34,6 +34,13 @@ class SEAuthorizationRouterSpecV2: BaseSpec { describe("AuthorizationsRouter") { context("when it's .list") { it("should create a valid url request") { + let data = SEBaseAuthenticatedRequestData( + url: baseUrl, + connectionGuid: "guid", + accessToken: accessToken, + appLanguage: "en" + ) + let expectedRequest = URLRequestBuilder.buildUrlRequest( with: baseUrl.appendingPathComponent(baseUrlPath), method: HTTPMethod.get.rawValue, @@ -41,7 +48,7 @@ class SEAuthorizationRouterSpecV2: BaseSpec { encoding: .url ) - let request = SEAuthorizationRouter.list(baseUrl, accessToken).asURLRequest() + let request = SEAuthorizationRouter.list(data).asURLRequest() expect(request).to(equal(expectedRequest)) } diff --git a/SaltedgeAuthenticatorSDK/Classes/Extensions/DictionaryExtensions.swift b/SaltedgeAuthenticatorSDK/Classes/Extensions/DictionaryExtensions.swift deleted file mode 100644 index a3b1edca..00000000 --- a/SaltedgeAuthenticatorSDK/Classes/Extensions/DictionaryExtensions.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -// DictionaryExtensions.swift -// This file is part of the Salt Edge Authenticator distribution -// (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 3 or later. -// -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -// For the additional permissions granted for Salt Edge Authenticator -// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md -// - -import Foundation - -extension Dictionary { - func merge(with other: Dictionary) -> Dictionary { - var copy = self - for (k, v) in other { - copy.updateValue(v, forKey: k) - } - return copy - } - - var jsonString: String? { - if let data = try? JSONSerialization.data(withJSONObject: self, options: []), - let string = String(data: data, encoding: String.Encoding.utf8) { - return string - } - return nil - } -} - -extension Dictionary where Key: Hashable, Value: Any { - static func == (lhs: [Key: Value], rhs: [Key: Value]) -> Bool { - return NSDictionary(dictionary: lhs).isEqual(to: rhs) - } -} From 191e4db023503269542404b35fe4c743b9ecd9be Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Fri, 4 Jun 2021 16:21:24 +0300 Subject: [PATCH 022/110] added specs --- .../SEEncryptedDataExtensionsSpec.swift | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/Example/Tests/Extensions/SEEncryptedDataExtensionsSpec.swift b/Example/Tests/Extensions/SEEncryptedDataExtensionsSpec.swift index 0df026a1..df80335a 100644 --- a/Example/Tests/Extensions/SEEncryptedDataExtensionsSpec.swift +++ b/Example/Tests/Extensions/SEEncryptedDataExtensionsSpec.swift @@ -24,10 +24,11 @@ import Quick import Nimble import SEAuthenticatorCore @testable import SEAuthenticator +@testable import SEAuthenticatorV2 class SEEncryptedDataExtensionsSpec: BaseSpec { override func spec() { - describe("decryptedAuthorizationData()") { + describe("decryptedAuthorizationData") { context("when given response is connect") { it("should return decrypted data from given response") { let connection = Connection() @@ -79,5 +80,31 @@ class SEEncryptedDataExtensionsSpec: BaseSpec { } } } + + describe("decryptedAuthorizationDataV2") { + it("should return decrypted data from given response") { + let connection = SpecUtils.createConnection(id: "2", apiVersion: "2") + let authorizationId = "00000" + + let authMessage = ["id": authorizationId, + "connection_id": connection.id, + "title": "Authorization", + "description": "Test authorization", + "created_at": Date().iso8601string, + "expires_at": Date().addingTimeInterval(5.0 * 60.0).iso8601string] + let encryptedData = try! SECryptoHelper.encrypt(authMessage.jsonString!, tag: SETagHelper.create(for: connection.guid)) + let dict: [String: Any] = [ + "data": encryptedData.data, + "key": encryptedData.key, + "iv": encryptedData.iv, + "id": Int(authorizationId)!, + "connection_id": Int(connection.id)!, + "status": "pending" + ] + let expectedData = SEAuthorizationData(authMessage) + + expect(expectedData).to(equal(SEEncryptedAuthorizationData(dict)!.decryptedAuthorizationData!)) + } + } } } From 0532fc3326cb63ed36d453a63ea145bf9e526bfc Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Mon, 7 Jun 2021 16:04:55 +0300 Subject: [PATCH 023/110] fixed confirm/deny flow --- .../v1/AuthorizationsInteractor.swift | 46 +++++++++++-------- .../AuthorizationsViewModel.swift | 2 + .../SingleAuthorizationViewModel.swift | 2 + .../Connections/ConnectionsViewModel.swift | 3 +- Example/Tests/v2/JWSHelperSpec.swift | 15 +++--- .../Routers/SEAuthorizationRouterSpecV2.swift | 36 +++++++++------ .../Models/SEConfirmAuthorizationData.swift | 6 +-- .../Managers/SEAuthorizationManagerV2.swift | 33 +++++++++---- .../API/Routers/SEAuthorizationRouter.swift | 28 +++++------ .../Classes/JWSHelper.swift | 4 +- .../Classes/RequestParametersBuilder.swift | 4 +- 11 files changed, 105 insertions(+), 74 deletions(-) rename SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/SEConfirmAuthorizationRequestData.swift => SaltedgeAuthenticatorCore/Classes/API/Models/SEConfirmAuthorizationData.swift (94%) diff --git a/Example/Authenticator/Utils/Interactors/v1/AuthorizationsInteractor.swift b/Example/Authenticator/Utils/Interactors/v1/AuthorizationsInteractor.swift index 2744eff5..e4b41d5e 100644 --- a/Example/Authenticator/Utils/Interactors/v1/AuthorizationsInteractor.swift +++ b/Example/Authenticator/Utils/Interactors/v1/AuthorizationsInteractor.swift @@ -27,35 +27,45 @@ import SEAuthenticatorCore struct AuthorizationsInteractor { static func confirm( + apiVersion: ApiVersion, data: SEConfirmAuthorizationRequestData, success: (() -> ())? = nil, failure: ((String) -> ())? = nil ) { - SEAuthorizationManager.confirmAuthorization( - data: data, - onSuccess: { _ in - success?() - }, - onFailure: { error in - failure?(error) - } - ) + if apiVersion == "2" { + SEAuthorizationManagerV2.confirmAuthorization( + data: data, + onSuccess: { _ in success?() }, + onFailure: { error in failure?(error) } + ) + } else { + SEAuthorizationManager.confirmAuthorization( + data: data, + onSuccess: { _ in success?() }, + onFailure: { error in failure?(error) } + ) + } } static func deny( + apiVersion: ApiVersion, data: SEConfirmAuthorizationRequestData, success: (() -> ())? = nil, failure: ((String) -> ())? = nil ) { - SEAuthorizationManager.denyAuthorization( - data: data, - onSuccess: { _ in - success?() - }, - onFailure: { error in - failure?(error) - } - ) + if apiVersion == "2" { + SEAuthorizationManagerV2.denyAuthorization( + data: data, + onSuccess: { _ in success?() }, + onFailure: { error in failure?(error) } + ) + } else { + SEAuthorizationManager.denyAuthorization( + data: data, + onSuccess: { _ in success?() }, + onFailure: { error in failure?(error) } + ) + } } static func refresh( diff --git a/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift b/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift index 6f9aec71..bba5e52b 100644 --- a/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift +++ b/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift @@ -108,6 +108,7 @@ class AuthorizationsViewModel { detailViewModel.state.value = .processing AuthorizationsInteractor.confirm( + apiVersion: detailViewModel.apiVersion, data: data, success: { detailViewModel.state.value = .success @@ -127,6 +128,7 @@ class AuthorizationsViewModel { detailViewModel.state.value = .processing AuthorizationsInteractor.deny( + apiVersion: detailViewModel.apiVersion, data: data, success: { detailViewModel.state.value = .denied diff --git a/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift b/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift index 03389dc2..0fbcdb75 100644 --- a/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift +++ b/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift @@ -97,6 +97,7 @@ extension SingleAuthorizationViewModel: AuthorizationDetailEventsDelegate { detailViewModel.state.value = .processing AuthorizationsInteractor.confirm( + apiVersion: detailViewModel.apiVersion, data: confirmData, success: { detailViewModel.state.value = .success @@ -132,6 +133,7 @@ extension SingleAuthorizationViewModel: AuthorizationDetailEventsDelegate { detailViewModel.state.value = .processing AuthorizationsInteractor.deny( + apiVersion: detailViewModel.apiVersion, data: confirmData, success: { detailViewModel.state.value = .denied diff --git a/Example/Authenticator/View Models/Connections/ConnectionsViewModel.swift b/Example/Authenticator/View Models/Connections/ConnectionsViewModel.swift index 66c0ddf7..8e2fbb13 100644 --- a/Example/Authenticator/View Models/Connections/ConnectionsViewModel.swift +++ b/Example/Authenticator/View Models/Connections/ConnectionsViewModel.swift @@ -94,11 +94,12 @@ final class ConnectionsViewModel { private func revoke(connection: Connection, interactor: BaseConnectionsInteractor) { interactor.revoke( connection, - success: { self.deleteConnection(connection: connection) }, + success: { }, failure: { error in self.delegate?.presentError(error) } ) + deleteConnection(connection: connection) } private func deleteConnection(connection: Connection) { diff --git a/Example/Tests/v2/JWSHelperSpec.swift b/Example/Tests/v2/JWSHelperSpec.swift index 31bfb9a7..9a288083 100644 --- a/Example/Tests/v2/JWSHelperSpec.swift +++ b/Example/Tests/v2/JWSHelperSpec.swift @@ -34,18 +34,15 @@ class JWSHelperSpec: BaseSpec { it("should return detached jws signature") { let expectedMessageDict = ["data": "Test Message"] - // create jws signature - let expectedJwsSignature = jwsSign(payload: expectedMessageDict, guid: connection.guid) - let splittedJwsSignature = expectedJwsSignature.split(separator: ".") - // get jws payload - let expectedSignaturePayload = splittedJwsSignature[1] - - // create actual jws signature + // create actual jws signature without payload let actualSignature = JWSHelper.sign(params: expectedMessageDict, guid: connection.guid)! let splittedActualSignature = actualSignature.split(separator: ".") - // insert jws payload into actual jws signature - let final = splittedActualSignature[0] + ".\(expectedSignaturePayload)." + splittedActualSignature[1] + // serialize expected payload + let jsonData = ParametersSerializer.createBody(parameters: expectedMessageDict)!.base64EncodedString() + + // insert expected payload into actual jws signature + let final = splittedActualSignature[0] + ".\(jsonData)." + splittedActualSignature[1] // verify data do { diff --git a/Example/Tests/v2/Networking/Routers/SEAuthorizationRouterSpecV2.swift b/Example/Tests/v2/Networking/Routers/SEAuthorizationRouterSpecV2.swift index d92ae162..f15cbad2 100644 --- a/Example/Tests/v2/Networking/Routers/SEAuthorizationRouterSpecV2.swift +++ b/Example/Tests/v2/Networking/Routers/SEAuthorizationRouterSpecV2.swift @@ -79,17 +79,22 @@ class SEAuthorizationRouterSpecV2: BaseSpec { context("when it's .confirm") { it("should create a valid url request") { - let data = SEAuthorizationRequestData( - id: "1", + let data = SEConfirmAuthorizationRequestData( + url: baseUrl, + connectionGuid: "123guid", + accessToken: accessToken, + appLanguage: "en", + authorizationId: "1", authorizationCode: "authorization_code", - userAuthorizationType: "passcode", geolocation: "GEO:", - connectionGuid: "123guid" + authorizationType: "passcode" ) + let parameters = RequestParametersBuilder.confirmAuthorizationParams( - encryptedData: data.encryptedData, + encryptedData: SEEncryptedData(data: "data", key: "key", iv: "iv"), exp: Date().addingTimeInterval(5.0 * 60.0).utcSeconds ) + let headers = Headers.signedRequestHeaders( token: accessToken, payloadParams: parameters, @@ -97,14 +102,14 @@ class SEAuthorizationRouterSpecV2: BaseSpec { ) let expectedRequest = URLRequestBuilder.buildUrlRequest( - with: baseUrl.appendingPathComponent("\(baseUrlPath)/\(data.id)/confirm"), + with: baseUrl.appendingPathComponent("\(baseUrlPath)/\(data.entityId)/confirm"), method: HTTPMethod.put.rawValue, headers: headers, params: parameters, encoding: .json ) - let request = SEAuthorizationRouter.confirm(baseUrl, accessToken, data).asURLRequest() + let request = SEAuthorizationRouter.confirm(data, parameters).asURLRequest() expect(request).to(equal(expectedRequest)) } @@ -112,15 +117,18 @@ class SEAuthorizationRouterSpecV2: BaseSpec { context("when it's .deny") { it("should create a valid url request") { - let data = SEAuthorizationRequestData( - id: "1", + let data = SEConfirmAuthorizationRequestData( + url: baseUrl, + connectionGuid: "123guid", + accessToken: accessToken, + appLanguage: "en", + authorizationId: "1", authorizationCode: "authorization_code", - userAuthorizationType: "passcode", geolocation: "GEO:", - connectionGuid: "123guid" + authorizationType: "passcode" ) let parameters = RequestParametersBuilder.confirmAuthorizationParams( - encryptedData: data.encryptedData, + encryptedData: SEEncryptedData(data: "data", key: "key", iv: "iv"), exp: Date().addingTimeInterval(5.0 * 60.0).utcSeconds ) let headers = Headers.signedRequestHeaders( @@ -130,14 +138,14 @@ class SEAuthorizationRouterSpecV2: BaseSpec { ) let expectedRequest = URLRequestBuilder.buildUrlRequest( - with: baseUrl.appendingPathComponent("\(baseUrlPath)/\(data.id)/deny"), + with: baseUrl.appendingPathComponent("\(baseUrlPath)/\(data.entityId)/deny"), method: HTTPMethod.put.rawValue, headers: headers, params: parameters, encoding: .json ) - let request = SEAuthorizationRouter.deny(baseUrl, accessToken, data).asURLRequest() + let request = SEAuthorizationRouter.deny(data, parameters).asURLRequest() expect(request).to(equal(expectedRequest)) } diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/SEConfirmAuthorizationRequestData.swift b/SaltedgeAuthenticatorCore/Classes/API/Models/SEConfirmAuthorizationData.swift similarity index 94% rename from SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/SEConfirmAuthorizationRequestData.swift rename to SaltedgeAuthenticatorCore/Classes/API/Models/SEConfirmAuthorizationData.swift index 8d4d9441..aa93871e 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/SEConfirmAuthorizationRequestData.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/Models/SEConfirmAuthorizationData.swift @@ -1,8 +1,8 @@ // -// SEConfirmAuthorizationRequestData.swift +// SEConfirmAuthorizationData // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -21,7 +21,6 @@ // import Foundation -import SEAuthenticatorCore public class SEConfirmAuthorizationRequestData: SEBaseAuthenticatedWithIdRequestData { public let authorizationCode: String? @@ -50,4 +49,3 @@ public class SEConfirmAuthorizationRequestData: SEBaseAuthenticatedWithIdRequest ) } } - diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEAuthorizationManagerV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEAuthorizationManagerV2.swift index 29f05a6c..127a8565 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEAuthorizationManagerV2.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEAuthorizationManagerV2.swift @@ -49,31 +49,46 @@ public struct SEAuthorizationManagerV2 { } public static func confirmAuthorization( - url: URL, - accessToken: AccessToken, - data: SEAuthorizationRequestData, + data: SEConfirmAuthorizationRequestData, onSuccess success: @escaping HTTPServiceSuccessClosure, onFailure failure: @escaping FailureBlock ) { + let parameters = RequestParametersBuilder.confirmAuthorizationParams( + encryptedData: encryptedData(requestData: data), + exp: Date().addingTimeInterval(5.0 * 60.0).utcSeconds + ) + HTTPService.execute( - request: SEAuthorizationRouter.confirm(url, accessToken, data), + request: SEAuthorizationRouter.confirm(data, parameters), success: success, failure: failure ) } public static func denyAuthorization( - url: URL, - accessToken: AccessToken, - data: SEAuthorizationRequestData, + data: SEConfirmAuthorizationRequestData, onSuccess success: @escaping HTTPServiceSuccessClosure, onFailure failure: @escaping FailureBlock ) { + let parameters = RequestParametersBuilder.confirmAuthorizationParams( + encryptedData: encryptedData(requestData: data), + exp: Date().addingTimeInterval(5.0 * 60.0).utcSeconds + ) + HTTPService.execute( - request: SEAuthorizationRouter.deny(url, accessToken, data), + request: SEAuthorizationRouter.deny(data, parameters), success: success, failure: failure ) } -} + private static func encryptedData(requestData: SEConfirmAuthorizationRequestData) -> SEEncryptedData? { + guard let data = [ + SENetKeys.authorizationCode: requestData.authorizationCode, + ApiConstants.userAuthorizationType: requestData.authorizationType, + ApiConstants.geolocation: requestData.geolocation + ].jsonString else { return nil } + + return try? SECryptoHelper.encrypt(data, tag: "\(requestData.connectionGuid)_provider_public_key") + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEAuthorizationRouter.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEAuthorizationRouter.swift index 27b8e39e..75d251ff 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEAuthorizationRouter.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEAuthorizationRouter.swift @@ -26,8 +26,8 @@ import SEAuthenticatorCore enum SEAuthorizationRouter: Routable { case list(SEBaseAuthenticatedRequestData) case show(SEBaseAuthenticatedWithIdRequestData) - case confirm(URL, AccessToken, SEAuthorizationRequestData) - case deny(URL, AccessToken, SEAuthorizationRequestData) + case confirm(SEConfirmAuthorizationRequestData, [String: Any]) + case deny(SEConfirmAuthorizationRequestData, [String: Any]) var method: HTTPMethod { switch self { @@ -53,13 +53,13 @@ enum SEAuthorizationRouter: Routable { return data.url.appendingPathComponent( "\(SENetPathBuilder(for: .authorizations, version: 2).path)/\(data.entityId)" ) - case .confirm(let url, _, let data): - return url.appendingPathComponent( - "\(SENetPathBuilder(for: .authorizations, version: 2).path)/\(data.id)/confirm" + case .confirm(let data, _): + return data.url.appendingPathComponent( + "\(SENetPathBuilder(for: .authorizations, version: 2).path)/\(data.entityId)/confirm" ) - case .deny(let url, _, let data): - return url.appendingPathComponent( - "\(SENetPathBuilder(for: .authorizations, version: 2).path)/\(data.id)/deny" + case .deny(let data, _): + return data.url.appendingPathComponent( + "\(SENetPathBuilder(for: .authorizations, version: 2).path)/\(data.entityId)/deny" ) } } @@ -70,10 +70,10 @@ enum SEAuthorizationRouter: Routable { return Headers.authorizedRequestHeaders(token: data.accessToken) case .show(let data): return Headers.authorizedRequestHeaders(token: data.accessToken) - case .confirm(_, let accessToken, let data), .deny(_, let accessToken, let data): + case .confirm(let data, let encryptedParameters), .deny(let data, let encryptedParameters): return Headers.signedRequestHeaders( - token: accessToken, - payloadParams: parameters, + token: data.accessToken, + payloadParams: encryptedParameters, connectionGuid: data.connectionGuid ) } @@ -82,11 +82,7 @@ enum SEAuthorizationRouter: Routable { var parameters: [String : Any]? { switch self { case .list, .show: return nil - case .confirm(_, _, let data), .deny(_, _, let data): - return RequestParametersBuilder.confirmAuthorizationParams( - encryptedData: data.encryptedData, - exp: Date().addingTimeInterval(5.0 * 60.0).utcSeconds - ) + case .confirm(_, let encryptedParameters), .deny(_, let encryptedParameters): return encryptedParameters } } } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/JWSHelper.swift b/SaltedgeAuthenticatorSDKv2/Classes/JWSHelper.swift index 539031d7..0aa61b1b 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/JWSHelper.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/JWSHelper.swift @@ -34,7 +34,9 @@ struct JWSHelper { let header = JWSHeader(algorithm: .RS256) guard let jws = try? JWS(header: header, payload: Payload(payloadBody), signer: signer) else { return nil } - + + print("JWS: ", jws.compactSerializedString) + let splittedSerializedJwsString = jws.compactSerializedString.split(separator: ".") return splittedSerializedJwsString[0] + ".." + splittedSerializedJwsString[2] diff --git a/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift b/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift index a405711a..43b706dd 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift @@ -70,9 +70,9 @@ struct RequestParametersBuilder { guard let encryptedData = encryptedData else { return [:] } let encryptedDataParams = [ - SENetKeys.data: encryptedData.data, + SENetKeys.iv: encryptedData.iv, SENetKeys.key: encryptedData.key, - SENetKeys.iv: encryptedData.iv + SENetKeys.data: encryptedData.data ] return [ From 9ecb14e627374cc85e303a671cde8858ca8eca4c Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Mon, 7 Jun 2021 16:32:03 +0300 Subject: [PATCH 024/110] fixed new authorizatiins sorting --- .../Models/Data Sources/AuthorizationsDataSource.swift | 4 ++-- .../Utils/Interactors/v1/CollectionsInteractor.swift | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift b/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift index 262147c7..797fb097 100644 --- a/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift +++ b/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift @@ -41,10 +41,10 @@ final class AuthorizationsDataSource { let viewModelsV2 = baseData.toAuthorizationViewModel(apiVersion: "2") .merge(array: self.viewModels.filter { $0.apiVersion == "2" }) - let allViewModels = viewModelsV1 + viewModelsV2 + let allViewModels = (viewModelsV1 + viewModelsV2).sorted(by: { $0.createdAt < $1.createdAt }) if allViewModels != self.viewModels { - self.viewModels = allViewModels.sorted(by: { $0.createdAt < $1.createdAt }) + self.viewModels = allViewModels return true } diff --git a/Example/Authenticator/Utils/Interactors/v1/CollectionsInteractor.swift b/Example/Authenticator/Utils/Interactors/v1/CollectionsInteractor.swift index 430de959..d1c60f4a 100644 --- a/Example/Authenticator/Utils/Interactors/v1/CollectionsInteractor.swift +++ b/Example/Authenticator/Utils/Interactors/v1/CollectionsInteractor.swift @@ -25,6 +25,7 @@ import SEAuthenticator import SEAuthenticatorV2 import SEAuthenticatorCore +// TODO: Remade to on interator enum CollectionsInteractor { case authorizations case consents From 4f2ac09ee4ac8c852e5d901757d505bed4d2bf52 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Mon, 7 Jun 2021 16:32:03 +0300 Subject: [PATCH 025/110] fixed new authorizatiins sorting --- .../Models/Data Sources/AuthorizationsDataSource.swift | 4 ++-- .../Utils/Interactors/v1/CollectionsInteractor.swift | 1 + .../Classes/API/Managers/SEAuthorizationManagerV2.swift | 1 + SaltedgeAuthenticatorSDKv2/Classes/JWSHelper.swift | 2 -- .../Classes/RequestParametersBuilder.swift | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift b/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift index 262147c7..797fb097 100644 --- a/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift +++ b/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift @@ -41,10 +41,10 @@ final class AuthorizationsDataSource { let viewModelsV2 = baseData.toAuthorizationViewModel(apiVersion: "2") .merge(array: self.viewModels.filter { $0.apiVersion == "2" }) - let allViewModels = viewModelsV1 + viewModelsV2 + let allViewModels = (viewModelsV1 + viewModelsV2).sorted(by: { $0.createdAt < $1.createdAt }) if allViewModels != self.viewModels { - self.viewModels = allViewModels.sorted(by: { $0.createdAt < $1.createdAt }) + self.viewModels = allViewModels return true } diff --git a/Example/Authenticator/Utils/Interactors/v1/CollectionsInteractor.swift b/Example/Authenticator/Utils/Interactors/v1/CollectionsInteractor.swift index 430de959..d1c60f4a 100644 --- a/Example/Authenticator/Utils/Interactors/v1/CollectionsInteractor.swift +++ b/Example/Authenticator/Utils/Interactors/v1/CollectionsInteractor.swift @@ -25,6 +25,7 @@ import SEAuthenticator import SEAuthenticatorV2 import SEAuthenticatorCore +// TODO: Remade to on interator enum CollectionsInteractor { case authorizations case consents diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEAuthorizationManagerV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEAuthorizationManagerV2.swift index 127a8565..75a1edd6 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEAuthorizationManagerV2.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEAuthorizationManagerV2.swift @@ -82,6 +82,7 @@ public struct SEAuthorizationManagerV2 { ) } + // Encrypt the confirmation payload with connection's provider public key private static func encryptedData(requestData: SEConfirmAuthorizationRequestData) -> SEEncryptedData? { guard let data = [ SENetKeys.authorizationCode: requestData.authorizationCode, diff --git a/SaltedgeAuthenticatorSDKv2/Classes/JWSHelper.swift b/SaltedgeAuthenticatorSDKv2/Classes/JWSHelper.swift index 0aa61b1b..4e85c1a7 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/JWSHelper.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/JWSHelper.swift @@ -35,8 +35,6 @@ struct JWSHelper { guard let jws = try? JWS(header: header, payload: Payload(payloadBody), signer: signer) else { return nil } - print("JWS: ", jws.compactSerializedString) - let splittedSerializedJwsString = jws.compactSerializedString.split(separator: ".") return splittedSerializedJwsString[0] + ".." + splittedSerializedJwsString[2] diff --git a/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift b/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift index 43b706dd..a405711a 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift @@ -70,9 +70,9 @@ struct RequestParametersBuilder { guard let encryptedData = encryptedData else { return [:] } let encryptedDataParams = [ - SENetKeys.iv: encryptedData.iv, + SENetKeys.data: encryptedData.data, SENetKeys.key: encryptedData.key, - SENetKeys.data: encryptedData.data + SENetKeys.iv: encryptedData.iv ] return [ From e8029435f2f8d7a773f64065ac17b32937f5dc75 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Thu, 10 Jun 2021 12:20:57 +0300 Subject: [PATCH 026/110] added possibility to build authorization content dynamically --- .../Authenticator.xcodeproj/project.pbxproj | 10 ++ .../Base.lproj/LaunchScreen.storyboard | 4 +- .../Utils/Components Styles/LabelStyles.swift | 1 + .../Extensions/StackViewExtensions.swift | 12 ++ .../Utils/Extensions/StringExtensions.swift | 8 ++ .../AuthorizationDetailViewModel.swift | 3 +- ...AuthorizationContentDynamicStackView.swift | 117 ++++++++++++++++++ .../AuthorizationContentView.swift | 51 ++++++-- .../Extensions/StringExtensionsSpec.swift | 36 ++++++ .../API/Models/SEAuthorizationDataV2.swift | 3 +- 10 files changed, 232 insertions(+), 13 deletions(-) create mode 100644 Example/Authenticator/Views/Authorizations/AuthorizationContentDynamicStackView.swift create mode 100644 Example/Tests/Extensions/StringExtensionsSpec.swift diff --git a/Example/Authenticator.xcodeproj/project.pbxproj b/Example/Authenticator.xcodeproj/project.pbxproj index b0e09287..c665922a 100644 --- a/Example/Authenticator.xcodeproj/project.pbxproj +++ b/Example/Authenticator.xcodeproj/project.pbxproj @@ -95,6 +95,9 @@ 9D630BC7243DC094000A9ACC /* FeatureCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D630BC5243DC094000A9ACC /* FeatureCellViewModel.swift */; }; 9D630BC9243DC464000A9ACC /* OnboardingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D630BC8243DC464000A9ACC /* OnboardingViewModel.swift */; }; 9D630BCA243DC464000A9ACC /* OnboardingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D630BC8243DC464000A9ACC /* OnboardingViewModel.swift */; }; + 9D63AB5826715B4C007EE1C8 /* StringExtensionsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D63AB5726715B4C007EE1C8 /* StringExtensionsSpec.swift */; }; + 9D63AB5A26721063007EE1C8 /* AuthorizationContentDynamicStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D63AB5926721063007EE1C8 /* AuthorizationContentDynamicStackView.swift */; }; + 9D63AB5B26721063007EE1C8 /* AuthorizationContentDynamicStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D63AB5926721063007EE1C8 /* AuthorizationContentDynamicStackView.swift */; }; 9D6575F8232A9B0200DCC53E /* DateExtensionsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D6575F7232A9B0200DCC53E /* DateExtensionsSpec.swift */; }; 9D6575FB232A9E8100DCC53E /* DateUtilsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D6575FA232A9E8100DCC53E /* DateUtilsSpec.swift */; }; 9D66E1A922D4C1C100BD59B6 /* AuthorizationDataSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D66E17B22D4C0AF00BD59B6 /* AuthorizationDataSpec.swift */; }; @@ -427,6 +430,8 @@ 9D5BCFE2248108F1009A6B40 /* AspectFitImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AspectFitImageView.swift; sourceTree = ""; }; 9D630BC5243DC094000A9ACC /* FeatureCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureCellViewModel.swift; sourceTree = ""; }; 9D630BC8243DC464000A9ACC /* OnboardingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewModel.swift; sourceTree = ""; }; + 9D63AB5726715B4C007EE1C8 /* StringExtensionsSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtensionsSpec.swift; sourceTree = ""; }; + 9D63AB5926721063007EE1C8 /* AuthorizationContentDynamicStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorizationContentDynamicStackView.swift; sourceTree = ""; }; 9D6575F7232A9B0200DCC53E /* DateExtensionsSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateExtensionsSpec.swift; sourceTree = ""; }; 9D6575FA232A9E8100DCC53E /* DateUtilsSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateUtilsSpec.swift; sourceTree = ""; }; 9D66E17122D4C0AF00BD59B6 /* CryptoErrorsSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CryptoErrorsSpec.swift; sourceTree = ""; }; @@ -793,6 +798,7 @@ children = ( 9DE3246922D398B300EB162A /* SEEncryptedDataExtensionsSpec.swift */, 9D6575F7232A9B0200DCC53E /* DateExtensionsSpec.swift */, + 9D63AB5726715B4C007EE1C8 /* StringExtensionsSpec.swift */, ); path = Extensions; sourceTree = ""; @@ -967,6 +973,7 @@ 9DF796452497C1F7001290DA /* AuthorizationHeaderView.swift */, 9DF796482497C2D2001290DA /* AuthorizationContentView.swift */, 9DFD634723BF366C0060B29F /* AuthorizationStateView.swift */, + 9D63AB5926721063007EE1C8 /* AuthorizationContentDynamicStackView.swift */, ); path = Authorizations; sourceTree = ""; @@ -1843,6 +1850,7 @@ 9DCED8F722CE18280050ED3C /* ModalView.swift in Sources */, 9DCED8E122CE18280050ED3C /* Localizations.swift in Sources */, 9DCED8DF22CE18280050ED3C /* AppSettings.swift in Sources */, + 9D63AB5A26721063007EE1C8 /* AuthorizationContentDynamicStackView.swift in Sources */, 959E30A0247BC78C0067354A /* LanguagePickerViewModel.swift in Sources */, 9DCED90C22CE18280050ED3C /* AuthorizationsCoordinator.swift in Sources */, 9DCED8FC22CE18280050ED3C /* CountdownProgressView.swift in Sources */, @@ -2072,11 +2080,13 @@ 9D6575F8232A9B0200DCC53E /* DateExtensionsSpec.swift in Sources */, 9DE3249E22D399F900EB162A /* DicitionaryExtensions.swift in Sources */, 9DE3247422D398ED00EB162A /* BaseSpec.swift in Sources */, + 9D63AB5B26721063007EE1C8 /* AuthorizationContentDynamicStackView.swift in Sources */, 95D784E723043949002BF219 /* RealmMigrationManager.swift in Sources */, 9DE3249A22D399EF00EB162A /* ColorExtensions.swift in Sources */, 9DE56A0624AA223B00FA5B2C /* ConsentCell.swift in Sources */, 9DA1C81524ADDF9F0024CDBA /* ConsentDetailViewController.swift in Sources */, 959E30B2247BDA250067354A /* LanguagePickerViewModelSpec.swift in Sources */, + 9D63AB5826715B4C007EE1C8 /* StringExtensionsSpec.swift in Sources */, 9D98746E24B3536400EE89BB /* ConsentSharedDataView.swift in Sources */, 9D1DEE7523D9C5D6003C79AC /* ConnectionPickerViewController.swift in Sources */, 9DBAD7A0247E55BD0023F944 /* QRCodeCoordinator.swift in Sources */, diff --git a/Example/Authenticator/Base.lproj/LaunchScreen.storyboard b/Example/Authenticator/Base.lproj/LaunchScreen.storyboard index 0b81ebeb..05c70f55 100644 --- a/Example/Authenticator/Base.lproj/LaunchScreen.storyboard +++ b/Example/Authenticator/Base.lproj/LaunchScreen.storyboard @@ -1,9 +1,9 @@ - + - + diff --git a/Example/Authenticator/Utils/Components Styles/LabelStyles.swift b/Example/Authenticator/Utils/Components Styles/LabelStyles.swift index 0102148c..46552b2f 100644 --- a/Example/Authenticator/Utils/Components Styles/LabelStyles.swift +++ b/Example/Authenticator/Utils/Components Styles/LabelStyles.swift @@ -30,5 +30,6 @@ extension UILabel { self.textAlignment = alignment self.numberOfLines = 0 self.lineBreakMode = .byWordWrapping + self.sizeToFit() } } diff --git a/Example/Authenticator/Utils/Extensions/StackViewExtensions.swift b/Example/Authenticator/Utils/Extensions/StackViewExtensions.swift index 5835794f..4f464507 100644 --- a/Example/Authenticator/Utils/Extensions/StackViewExtensions.swift +++ b/Example/Authenticator/Utils/Extensions/StackViewExtensions.swift @@ -39,4 +39,16 @@ extension UIStackView { func addArrangedSubviews(_ subviews: UIView...) { subviews.forEach { addArrangedSubview($0) } } + + func addArrangedSubviews(_ subviews: [UIView]) { + subviews.forEach { addArrangedSubview($0) } + } + + func removeAllArrangedSubviews() { + arrangedSubviews.forEach { + removeArrangedSubview($0) + NSLayoutConstraint.deactivate($0.constraints) + $0.removeFromSuperview() + } + } } diff --git a/Example/Authenticator/Utils/Extensions/StringExtensions.swift b/Example/Authenticator/Utils/Extensions/StringExtensions.swift index 76154419..37c78036 100644 --- a/Example/Authenticator/Utils/Extensions/StringExtensions.swift +++ b/Example/Authenticator/Utils/Extensions/StringExtensions.swift @@ -43,6 +43,14 @@ extension String { return attributedString } + + func capitalizingFirstLetter() -> String { + return prefix(1).uppercased() + self.lowercased().dropFirst() + } + + mutating func capitalizeFirstLetter() { + self = self.capitalizingFirstLetter() + } } private extension NSAttributedString { diff --git a/Example/Authenticator/View Models/Authorizations/AuthorizationDetailViewModel.swift b/Example/Authenticator/View Models/Authorizations/AuthorizationDetailViewModel.swift index 6643b47c..34f38e6e 100644 --- a/Example/Authenticator/View Models/Authorizations/AuthorizationDetailViewModel.swift +++ b/Example/Authenticator/View Models/Authorizations/AuthorizationDetailViewModel.swift @@ -36,6 +36,7 @@ final class AuthorizationDetailViewModel: Equatable { var authorizationId: String var connectionId: String var description: String = "" + var descriptionAttributes: [String: Any] = [:] var authorizationCode: String? var lifetime: Int = 0 var authorizationExpiresAt: Date = Date() @@ -55,7 +56,7 @@ final class AuthorizationDetailViewModel: Equatable { self.description = dataV1.description } else if let dataV2 = data as? SEAuthorizationDataV2 { self.title = dataV2.title - self.description = "This is V2" // TODO: Fix description + self.descriptionAttributes = dataV2.description } self.apiVersion = apiVersion self.authorizationId = data.id diff --git a/Example/Authenticator/Views/Authorizations/AuthorizationContentDynamicStackView.swift b/Example/Authenticator/Views/Authorizations/AuthorizationContentDynamicStackView.swift new file mode 100644 index 00000000..2dc2679b --- /dev/null +++ b/Example/Authenticator/Views/Authorizations/AuthorizationContentDynamicStackView.swift @@ -0,0 +1,117 @@ +// +// AuthorizationContentDynamicStackView +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import UIKit + +private enum FieldType: String { + case payment + case extra +} + +/* + The UIStackView which is designed to construct the authorization content dynamically. + */ +final class AuthorizationContentDynamicStackView: UIStackView { + init() { + super.init(frame: .zero) + axis = .vertical + alignment = .fill + distribution = .fillProportionally + spacing = 8.0 + } + + /* + Setup the stackView content using the authorization description attributes. + + The content will be constructed using next attributes: + - payment + - text + - extra + + - parameters: + - attributes: Authorization description dictionary with nested attributes dictionaries + */ + func setup(using attributes: [String: Any]) { + removeAllArrangedSubviews() + + if let paymentDict = attributes["payment"] as? [String: String] { + for (key, value) in paymentDict { + addArrangedSubview(contentView(title: key, description: value, fieldType: .payment)) + } + } + if let text = attributes["text"] as? String { + let label = UILabel( + font: .systemFont(ofSize: 16.0, weight: .regular), + alignment: .left, + textColor: .titleColor + ) + label.text = text + addArrangedSubview(label) + } + if let extraDict = attributes["extra"] as? [String: String] { + for (key, value) in extraDict { + addArrangedSubview(contentView(title: key, description: value, fieldType: .extra)) + } + } + } + + private func contentView(title: String? = nil, description: String, fieldType: FieldType) -> UIView { + let contentView = UIView() + let contentTitleLabel = UILabel( + font: .systemFont(ofSize: 16.0, weight: .regular), + textColor: fieldType == .extra ? .dark60 : .titleColor + ) + let descriptionLabel = UILabel( + font: .systemFont(ofSize: 16.0, weight: .regular), + textColor: fieldType == .extra ? .dark60 : .titleColor + ) + + var title = title?.replacingOccurrences(of: "_", with: " ").capitalizingFirstLetter() ?? "" + + if fieldType == .extra { + title = "\(title):" + } + + contentTitleLabel.text = title + descriptionLabel.text = description + + contentView.addSubviews(contentTitleLabel, descriptionLabel) + + contentTitleLabel.leftToSuperview() + contentTitleLabel.centerYToSuperview() + descriptionLabel.centerYToSuperview() + + if fieldType == .payment { + descriptionLabel.leftToRight(of: contentTitleLabel, offset: 32.0, relation: .equalOrGreater) + descriptionLabel.rightToSuperview(offset: -16.0) + } else { + descriptionLabel.leftToRight(of: contentTitleLabel, offset: 6.0) + } + contentView.height(18.0) + + return contentView + } + + required init(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Example/Authenticator/Views/Authorizations/AuthorizationContentView.swift b/Example/Authenticator/Views/Authorizations/AuthorizationContentView.swift index 24c0c131..382d8da4 100644 --- a/Example/Authenticator/Views/Authorizations/AuthorizationContentView.swift +++ b/Example/Authenticator/Views/Authorizations/AuthorizationContentView.swift @@ -36,6 +36,7 @@ final class AuthorizationContentView: UIView { private let titleLabel = UILabel(font: .systemFont(ofSize: 24.0, weight: .regular), textColor: .titleColor) private lazy var descriptionTextView = UITextView() + private lazy var attributesStackView = AuthorizationContentDynamicStackView() private lazy var webView: WKWebView = { let webView = WKWebView(frame: .zero, configuration: WKWebViewConfiguration()) webView.layer.masksToBounds = true @@ -46,6 +47,7 @@ final class AuthorizationContentView: UIView { let stackView = UIStackView() stackView.axis = .vertical stackView.spacing = Layout.sideOffset + stackView.distribution = .fillProportionally return stackView }() private var buttonsStackView: UIStackView = { @@ -71,16 +73,10 @@ final class AuthorizationContentView: UIView { } else { stateView.set(state: .base) - if viewModel.description.htmlToAttributedString != nil { - let supportDarkCSS = "" - - contentStackView.removeArrangedSubview(descriptionTextView) - webView.loadHTMLString(viewModel.description + supportDarkCSS, baseURL: nil) - contentStackView.addArrangedSubview(webView) + if viewModel.apiVersion == "1" { + setupContentV1() } else { - contentStackView.removeArrangedSubview(webView) - descriptionTextView.text = viewModel.description - contentStackView.addArrangedSubview(descriptionTextView) + setupContentV2(using: viewModel.descriptionAttributes) } } @@ -101,6 +97,35 @@ final class AuthorizationContentView: UIView { layout() } + private func setupContentV1() { + if viewModel.description.htmlToAttributedString != nil { + setupWebView(content: viewModel.description) + } else { + contentStackView.removeArrangedSubview(webView) + descriptionTextView.text = viewModel.description + contentStackView.addArrangedSubview(descriptionTextView) + } + } + + private func setupContentV2(using attributes: [String: Any]) { + if let html = attributes["html"] as? String { + setupWebView(content: html) + } else { + contentStackView.removeAllArrangedSubviews() + + let attributesView = UIView() + attributesView.addSubview(attributesStackView) + contentStackView.addArrangedSubview(attributesView) + + attributesView.edgesToSuperview() + + attributesStackView.topToSuperview() + attributesStackView.widthToSuperview() + + attributesStackView.setup(using: attributes) + } + } + required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -117,6 +142,14 @@ private extension AuthorizationContentView { buttonsStackView.addArrangedSubviews(leftButton, rightButton) } + + func setupWebView(content: String) { + let supportDarkCSS = "" + + contentStackView.removeAllArrangedSubviews() + webView.loadHTMLString(content + supportDarkCSS, baseURL: nil) + contentStackView.addArrangedSubview(webView) + } } // MARK: - Actions diff --git a/Example/Tests/Extensions/StringExtensionsSpec.swift b/Example/Tests/Extensions/StringExtensionsSpec.swift new file mode 100644 index 00000000..0e6183cb --- /dev/null +++ b/Example/Tests/Extensions/StringExtensionsSpec.swift @@ -0,0 +1,36 @@ +// +// StringExtensionsSpec +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Quick +import Nimble + +class StringExtensionsSpec: BaseSpec { + override func spec() { + describe("capitalizingFirstLetter") { + it("should capitalize only first word from sentence") { + let testString = "hello world" + + expect(testString.capitalizingFirstLetter()).to(equal("Hello world")) + } + } + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEAuthorizationDataV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEAuthorizationDataV2.swift index d1d2040f..1a688896 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEAuthorizationDataV2.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEAuthorizationDataV2.swift @@ -29,6 +29,7 @@ public class SEAuthorizationDataV2: SEBaseAuthorizationData { public var createdAt: Date public var expiresAt: Date public var authorizationCode: String? + public var extra: [String: String]? public var id: String public var connectionId: String @@ -66,6 +67,6 @@ extension SEAuthorizationDataV2: Equatable { } } -private func ==(lhs: [String: Any], rhs: [String: Any] ) -> Bool { +private func ==(lhs: [String: Any], rhs: [String: Any]) -> Bool { return NSDictionary(dictionary: lhs).isEqual(to: rhs) } From cc72746b5dd01294cd11a2eb18290d9db7871b82 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Thu, 10 Jun 2021 12:29:46 +0300 Subject: [PATCH 027/110] added spec --- .../Authenticator.xcodeproj/project.pbxproj | 4 + .../Extensions/StackViewExtensions.swift | 4 - .../Extensions/StackViewExtensionsSpec.swift | 76 +++++++++++++++++++ .../API/Models/SEAuthorizationDataV2.swift | 1 - 4 files changed, 80 insertions(+), 5 deletions(-) create mode 100644 Example/Tests/Extensions/StackViewExtensionsSpec.swift diff --git a/Example/Authenticator.xcodeproj/project.pbxproj b/Example/Authenticator.xcodeproj/project.pbxproj index c665922a..60f9a320 100644 --- a/Example/Authenticator.xcodeproj/project.pbxproj +++ b/Example/Authenticator.xcodeproj/project.pbxproj @@ -98,6 +98,7 @@ 9D63AB5826715B4C007EE1C8 /* StringExtensionsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D63AB5726715B4C007EE1C8 /* StringExtensionsSpec.swift */; }; 9D63AB5A26721063007EE1C8 /* AuthorizationContentDynamicStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D63AB5926721063007EE1C8 /* AuthorizationContentDynamicStackView.swift */; }; 9D63AB5B26721063007EE1C8 /* AuthorizationContentDynamicStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D63AB5926721063007EE1C8 /* AuthorizationContentDynamicStackView.swift */; }; + 9D63AB5D2672123A007EE1C8 /* StackViewExtensionsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D63AB5C2672123A007EE1C8 /* StackViewExtensionsSpec.swift */; }; 9D6575F8232A9B0200DCC53E /* DateExtensionsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D6575F7232A9B0200DCC53E /* DateExtensionsSpec.swift */; }; 9D6575FB232A9E8100DCC53E /* DateUtilsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D6575FA232A9E8100DCC53E /* DateUtilsSpec.swift */; }; 9D66E1A922D4C1C100BD59B6 /* AuthorizationDataSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D66E17B22D4C0AF00BD59B6 /* AuthorizationDataSpec.swift */; }; @@ -432,6 +433,7 @@ 9D630BC8243DC464000A9ACC /* OnboardingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewModel.swift; sourceTree = ""; }; 9D63AB5726715B4C007EE1C8 /* StringExtensionsSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtensionsSpec.swift; sourceTree = ""; }; 9D63AB5926721063007EE1C8 /* AuthorizationContentDynamicStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorizationContentDynamicStackView.swift; sourceTree = ""; }; + 9D63AB5C2672123A007EE1C8 /* StackViewExtensionsSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackViewExtensionsSpec.swift; sourceTree = ""; }; 9D6575F7232A9B0200DCC53E /* DateExtensionsSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateExtensionsSpec.swift; sourceTree = ""; }; 9D6575FA232A9E8100DCC53E /* DateUtilsSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateUtilsSpec.swift; sourceTree = ""; }; 9D66E17122D4C0AF00BD59B6 /* CryptoErrorsSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CryptoErrorsSpec.swift; sourceTree = ""; }; @@ -799,6 +801,7 @@ 9DE3246922D398B300EB162A /* SEEncryptedDataExtensionsSpec.swift */, 9D6575F7232A9B0200DCC53E /* DateExtensionsSpec.swift */, 9D63AB5726715B4C007EE1C8 /* StringExtensionsSpec.swift */, + 9D63AB5C2672123A007EE1C8 /* StackViewExtensionsSpec.swift */, ); path = Extensions; sourceTree = ""; @@ -2097,6 +2100,7 @@ 959E30B1247BDA250067354A /* SettingsViewModelSpec.swift in Sources */, 9DE3247322D398ED00EB162A /* URLRequestBuilder.swift in Sources */, 9DE3247522D398ED00EB162A /* DataFixtures.swift in Sources */, + 9D63AB5D2672123A007EE1C8 /* StackViewExtensionsSpec.swift in Sources */, 9D66E1DF22D6146900BD59B6 /* AuthorizationRouterSpec.swift in Sources */, 9DE324D222D39AA700EB162A /* PasscodeSymbolView.swift in Sources */, 9D98747D24B390F200EE89BB /* ConsentsInteractor.swift in Sources */, diff --git a/Example/Authenticator/Utils/Extensions/StackViewExtensions.swift b/Example/Authenticator/Utils/Extensions/StackViewExtensions.swift index 4f464507..a1fd82be 100644 --- a/Example/Authenticator/Utils/Extensions/StackViewExtensions.swift +++ b/Example/Authenticator/Utils/Extensions/StackViewExtensions.swift @@ -40,10 +40,6 @@ extension UIStackView { subviews.forEach { addArrangedSubview($0) } } - func addArrangedSubviews(_ subviews: [UIView]) { - subviews.forEach { addArrangedSubview($0) } - } - func removeAllArrangedSubviews() { arrangedSubviews.forEach { removeArrangedSubview($0) diff --git a/Example/Tests/Extensions/StackViewExtensionsSpec.swift b/Example/Tests/Extensions/StackViewExtensionsSpec.swift new file mode 100644 index 00000000..c12e7b50 --- /dev/null +++ b/Example/Tests/Extensions/StackViewExtensionsSpec.swift @@ -0,0 +1,76 @@ +// +// StackViewExtensionsSpec +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Quick +import Nimble +import UIKit + +class StackViewExtensionsSpec: BaseSpec { + override func spec() { + let stackView = UIStackView() + + describe("init()") { + it("should initialize an stackView with given properties") { + let axis = NSLayoutConstraint.Axis.vertical + let alignment = UIStackView.Alignment.fill + let spacing: CGFloat = 15.0 + let distribution = UIStackView.Distribution.equalSpacing + stackView.axis = axis + stackView.alignment = alignment + stackView.spacing = spacing + stackView.distribution = distribution + stackView.translatesAutoresizingMaskIntoConstraints = false + + let actualStackView = UIStackView(axis: axis, alignment: alignment, spacing: spacing, distribution: distribution) + + expect(stackView.axis).to(equal(actualStackView.axis)) + expect(stackView.alignment).to(equal(actualStackView.alignment)) + expect(stackView.spacing).to(equal(actualStackView.spacing)) + expect(stackView.distribution).to(equal(actualStackView.distribution)) + } + } + + describe("removeAllArrangedSubviews") { + it("should remove all arranged subviews of stackView") { + stackView.addArrangedSubviews(UIView(), UIView()) + + expect(stackView.arrangedSubviews.count).to(equal(2)) + + stackView.removeAllArrangedSubviews() + + expect(stackView.arrangedSubviews).to(beEmpty()) + } + } + + describe("addArrangedSubviews") { + it("should add subviews to stackView") { + let firstView = UIView() + let secondView = UIView() + let thirdView = UIView() + stackView.addArrangedSubviews(firstView, secondView, thirdView) + + expect(stackView.arrangedSubviews).to(equal([firstView, secondView, thirdView])) + } + } + } +} + diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEAuthorizationDataV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEAuthorizationDataV2.swift index 1a688896..92883a59 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEAuthorizationDataV2.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEAuthorizationDataV2.swift @@ -29,7 +29,6 @@ public class SEAuthorizationDataV2: SEBaseAuthorizationData { public var createdAt: Date public var expiresAt: Date public var authorizationCode: String? - public var extra: [String: String]? public var id: String public var connectionId: String From 2d2269a19340824141d6ac8f7e50d06d53e0d764 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Thu, 10 Jun 2021 18:11:33 +0300 Subject: [PATCH 028/110] fixed authorization attributes order and colors --- .../extraTextColor.colorset/Contents.json | 20 +++++++ .../en.lproj/Authenticator.strings | 1 + .../Utils/Extensions/ColorExtensions.swift | 4 ++ .../Utils/Helpers/Localizations.swift | 2 + ...AuthorizationContentDynamicStackView.swift | 55 +++++++++++++++---- .../Classes/API/SENetKeys.swift | 15 +++++ 6 files changed, 85 insertions(+), 12 deletions(-) create mode 100644 Example/Authenticator/Assets.xcassets/Colors/extraTextColor.colorset/Contents.json diff --git a/Example/Authenticator/Assets.xcassets/Colors/extraTextColor.colorset/Contents.json b/Example/Authenticator/Assets.xcassets/Colors/extraTextColor.colorset/Contents.json new file mode 100644 index 00000000..9a82cf52 --- /dev/null +++ b/Example/Authenticator/Assets.xcassets/Colors/extraTextColor.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.729", + "green" : "0.635", + "red" : "0.506" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/Authenticator/Supporting Files/Localization/en.lproj/Authenticator.strings b/Example/Authenticator/Supporting Files/Localization/en.lproj/Authenticator.strings index c606727e..9d18b497 100644 --- a/Example/Authenticator/Supporting Files/Localization/en.lproj/Authenticator.strings +++ b/Example/Authenticator/Supporting Files/Localization/en.lproj/Authenticator.strings @@ -71,6 +71,7 @@ "warnings.inactivity_block_message" = "Application will be locked due to inactivity"; "in_app.connections_list.delete_all_connections" = "Delete all connections and stop receiving authorizations for all connections."; "in_app.connections_list.delete_connection" = "Are you sure you want to delete connection to your provider? This action cannot be undone."; +"in_app.authorizations.ip_address" = "IP address"; "in_app.authorizations.authorization_expired" = "Authorization expired"; "in_app.authorizations.no_authorizations" = "Nothing to authorize"; "in_app.authorizations.no_authorizations_description" = "You don’t have any active authorization requests yet. Scan QR code for instant action or to connect new provider."; diff --git a/Example/Authenticator/Utils/Extensions/ColorExtensions.swift b/Example/Authenticator/Utils/Extensions/ColorExtensions.swift index 21d47321..1d556eaf 100644 --- a/Example/Authenticator/Utils/Extensions/ColorExtensions.swift +++ b/Example/Authenticator/Utils/Extensions/ColorExtensions.swift @@ -71,6 +71,10 @@ extension UIColor { return UIColor(named: "dark80_grey100", in: .authenticator_main, compatibleWith: nil)! } + static var extraTextColor: UIColor { + return UIColor(named: "extraTextColor", in: .authenticator_main, compatibleWith: nil)! + } + static var white_dark100: UIColor { return UIColor(named: "white_dark100", in: .authenticator_main, compatibleWith: nil)! } diff --git a/Example/Authenticator/Utils/Helpers/Localizations.swift b/Example/Authenticator/Utils/Helpers/Localizations.swift index 8e3d9151..1bba32ab 100644 --- a/Example/Authenticator/Utils/Helpers/Localizations.swift +++ b/Example/Authenticator/Utils/Helpers/Localizations.swift @@ -107,6 +107,8 @@ enum Localizations: String, Localizable { case forgotPasscodeClearDataDescription = "no_data.forgot_passcode_clear_data_description" case locationWarning = "location.warning" + case ipAddress = "in_app.authorizations.ip_address" + // MARK: - Actions case newAction = "instant_action.new_action" case instantActionSuccessMessage = "instant_action.success_message" diff --git a/Example/Authenticator/Views/Authorizations/AuthorizationContentDynamicStackView.swift b/Example/Authenticator/Views/Authorizations/AuthorizationContentDynamicStackView.swift index 2dc2679b..73503200 100644 --- a/Example/Authenticator/Views/Authorizations/AuthorizationContentDynamicStackView.swift +++ b/Example/Authenticator/Views/Authorizations/AuthorizationContentDynamicStackView.swift @@ -21,16 +21,32 @@ // import UIKit +import SEAuthenticatorCore private enum FieldType: String { case payment case extra + case text + + var textColor: UIColor { + switch self { + case .payment: return .dark80_grey100 + case .extra: return .extraTextColor + case .text: return .titleColor + } + } } /* The UIStackView which is designed to construct the authorization content dynamically. */ final class AuthorizationContentDynamicStackView: UIStackView { + private let paymentSortedKeys = [ + SENetKeys.payee, SENetKeys.amount, SENetKeys.account, + SENetKeys.paymentDate, SENetKeys.reference, SENetKeys.fee, SENetKeys.exchangeRate + ] + private let extraSortedKeys = [SENetKeys.actionDate, SENetKeys.device, SENetKeys.location, SENetKeys.ip] + init() { super.init(frame: .zero) axis = .vertical @@ -53,42 +69,57 @@ final class AuthorizationContentDynamicStackView: UIStackView { func setup(using attributes: [String: Any]) { removeAllArrangedSubviews() - if let paymentDict = attributes["payment"] as? [String: String] { - for (key, value) in paymentDict { - addArrangedSubview(contentView(title: key, description: value, fieldType: .payment)) + if let paymentDict = attributes[SENetKeys.payment] as? [String: Any] { + paymentSortedKeys.forEach { + addArrangedSubview(inputDict: paymentDict, key: $0, type: .payment) } } - if let text = attributes["text"] as? String { + if let text = attributes[SENetKeys.text] as? String { let label = UILabel( font: .systemFont(ofSize: 16.0, weight: .regular), alignment: .left, - textColor: .titleColor + textColor: FieldType.text.textColor ) label.text = text addArrangedSubview(label) } - if let extraDict = attributes["extra"] as? [String: String] { - for (key, value) in extraDict { - addArrangedSubview(contentView(title: key, description: value, fieldType: .extra)) + if let extraDict = attributes[SENetKeys.extra] as? [String: Any] { + // Adding empty view as separator between blocks + let emptyView = UIView() + emptyView.height(16.0) + addArrangedSubview(emptyView) + + extraSortedKeys.forEach { + addArrangedSubview(inputDict: extraDict, key: $0, type: .extra) } } } + private func addArrangedSubview(inputDict: [String: Any]?, key: String, type: FieldType) { + if let value = inputDict?[key] as? String, !value.isEmpty { + addArrangedSubview(contentView(title: key, description: value, fieldType: type)) + } + } + private func contentView(title: String? = nil, description: String, fieldType: FieldType) -> UIView { let contentView = UIView() let contentTitleLabel = UILabel( font: .systemFont(ofSize: 16.0, weight: .regular), - textColor: fieldType == .extra ? .dark60 : .titleColor + textColor: fieldType.textColor ) let descriptionLabel = UILabel( font: .systemFont(ofSize: 16.0, weight: .regular), - textColor: fieldType == .extra ? .dark60 : .titleColor + textColor: fieldType.textColor ) var title = title?.replacingOccurrences(of: "_", with: " ").capitalizingFirstLetter() ?? "" if fieldType == .extra { - title = "\(title):" + if title == SENetKeys.ip.capitalizingFirstLetter() { + title = l10n(.ipAddress) + } + + title += ":" } contentTitleLabel.text = title @@ -106,7 +137,7 @@ final class AuthorizationContentDynamicStackView: UIStackView { } else { descriptionLabel.leftToRight(of: contentTitleLabel, offset: 6.0) } - contentView.height(18.0) + contentView.height(fieldType == .payment ? 24.0 : 18.0) return contentView } diff --git a/SaltedgeAuthenticatorCore/Classes/API/SENetKeys.swift b/SaltedgeAuthenticatorCore/Classes/API/SENetKeys.swift index 5caf658a..6b7dc2af 100644 --- a/SaltedgeAuthenticatorCore/Classes/API/SENetKeys.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/SENetKeys.swift @@ -76,4 +76,19 @@ public struct SENetKeys { public static let iban = "iban" public static let balance = "balance" public static let transactions = "transactions" + + public static let payment = "payment" + public static let extra = "extra" + public static let text = "text" + public static let payee = "payee" + public static let amount = "amount" + public static let account = "account" + public static let paymentDate = "payment_date" + public static let reference = "reference" + public static let fee = "fee" + public static let exchangeRate = "exchange_rate" + public static let actionDate = "action_date" + public static let device = "device" + public static let location = "location" + public static let ip = "ip" } From b8aa3425155753b6e98a8b90bbb02f9140846ed4 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Thu, 10 Jun 2021 18:19:33 +0300 Subject: [PATCH 029/110] fixed payment values color --- .../Authorizations/AuthorizationContentDynamicStackView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Example/Authenticator/Views/Authorizations/AuthorizationContentDynamicStackView.swift b/Example/Authenticator/Views/Authorizations/AuthorizationContentDynamicStackView.swift index 73503200..396e2c08 100644 --- a/Example/Authenticator/Views/Authorizations/AuthorizationContentDynamicStackView.swift +++ b/Example/Authenticator/Views/Authorizations/AuthorizationContentDynamicStackView.swift @@ -109,7 +109,7 @@ final class AuthorizationContentDynamicStackView: UIStackView { ) let descriptionLabel = UILabel( font: .systemFont(ofSize: 16.0, weight: .regular), - textColor: fieldType.textColor + textColor: fieldType == .payment ? .titleColor : FieldType.extra.textColor ) var title = title?.replacingOccurrences(of: "_", with: " ").capitalizingFirstLetter() ?? "" From 95c84486f9fe695e79ead50b2898fdf99a0c3361 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Mon, 14 Jun 2021 11:30:41 +0300 Subject: [PATCH 030/110] fixed authorization from push notification --- .../Supporting Files/AppDelegate.swift | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Example/Authenticator/Supporting Files/AppDelegate.swift b/Example/Authenticator/Supporting Files/AppDelegate.swift index 97e222db..3b2e8b19 100644 --- a/Example/Authenticator/Supporting Files/AppDelegate.swift +++ b/Example/Authenticator/Supporting Files/AppDelegate.swift @@ -161,9 +161,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD let userInfo = request.content.userInfo guard let apsDict = userInfo[SENetKeys.aps] as? [String: Any], - let dataDict = apsDict[SENetKeys.data] as? [String: Any], - let connectionId = dataDict[SENetKeys.connectionId] as? String, - let authorizationId = dataDict[SENetKeys.authorizationId] as? String else { return nil } - return (connectionId, authorizationId) + let dataDict = apsDict[SENetKeys.data] as? [String: Any] else { return nil } + + // NOTE: connection_id and authorization_id from v1 are strings, from v2 - ints + if let connectionId = dataDict[SENetKeys.connectionId] as? String, + let authorizationId = dataDict[SENetKeys.authorizationId] as? String { + return (connectionId, authorizationId) + } else if let connectionId = dataDict[SENetKeys.connectionId] as? Int, + let authorizationId = dataDict[SENetKeys.authorizationId] as? Int { + return ("\(connectionId)", "\(authorizationId)") + } else { + return nil + } } } From 565f9dfc897ca6221cf5eba0299f76a0b9216624 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Tue, 15 Jun 2021 11:17:23 +0300 Subject: [PATCH 031/110] changed geolocation flow --- .../descriptionYellow.colorset/Contents.json | 20 ++++++ .../AuthorizationsCoordinator.swift | 6 +- .../AuthorizationsDataSource.swift | 6 -- .../en.lproj/Authenticator.strings | 6 ++ .../Utils/Extensions/ColorExtensions.swift | 4 ++ .../Utils/Helpers/Localizations.swift | 7 ++ .../Utils/Helpers/LocationManager.swift | 9 ++- .../Utils/Helpers/NotificationsHelper.swift | 1 + .../AuthorizationsViewController.swift | 31 ++++++-- .../ConnectionsViewController.swift | 38 ++++++++-- .../AuthorizationDetailViewModel.swift | 6 ++ .../AuthorizationsViewModel.swift | 69 ++++++++++-------- .../SingleAuthorizationViewModel.swift | 2 +- .../Connections/ConnectionCellViewModel.swift | 19 ++++- .../AuthorizationsDataSourceSpec.swift | 4 +- .../Spec Helpers/MockLocationManager.swift | 2 +- Example/Tests/Spec Helpers/SpecUtils.swift | 4 +- .../AuthorizationsViewModelSpec.swift | 70 +++++++++++++++++-- 18 files changed, 241 insertions(+), 63 deletions(-) create mode 100644 Example/Authenticator/Assets.xcassets/Colors/descriptionYellow.colorset/Contents.json diff --git a/Example/Authenticator/Assets.xcassets/Colors/descriptionYellow.colorset/Contents.json b/Example/Authenticator/Assets.xcassets/Colors/descriptionYellow.colorset/Contents.json new file mode 100644 index 00000000..43c016c1 --- /dev/null +++ b/Example/Authenticator/Assets.xcassets/Colors/descriptionYellow.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.180", + "green" : "0.750", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/Authenticator/Coordinators/AuthorizationsCoordinator.swift b/Example/Authenticator/Coordinators/AuthorizationsCoordinator.swift index c89b07d6..99fd44d0 100644 --- a/Example/Authenticator/Coordinators/AuthorizationsCoordinator.swift +++ b/Example/Authenticator/Coordinators/AuthorizationsCoordinator.swift @@ -25,7 +25,7 @@ import SEAuthenticator final class AuthorizationsCoordinator: Coordinator { let rootViewController = AuthorizationsViewController() - private let dataSource = AuthorizationsDataSource(locationManagement: LocationManager.shared) + private let dataSource = AuthorizationsDataSource() private let viewModel = AuthorizationsViewModel() private var qrCoordinator: QRCodeCoordinator? @@ -95,4 +95,8 @@ extension AuthorizationsCoordinator: AuthorizationsViewControllerDelegate { rootViewController.present(actionSheet, animated: true) } + + func requestLocation() { + LocationManager.shared.requestLocationAuthorization() + } } diff --git a/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift b/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift index 797fb097..54e882e2 100644 --- a/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift +++ b/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift @@ -27,12 +27,6 @@ import SEAuthenticatorCore final class AuthorizationsDataSource { private var viewModels = [AuthorizationDetailViewModel]() - private var locationManagement: LocationManagement - - // TODO: Fix geolocation according to new design - init(locationManagement: LocationManagement) { - self.locationManagement = locationManagement - } func update(with baseData: [SEBaseAuthorizationData]) -> Bool { let viewModelsV1 = baseData.toAuthorizationViewModel(apiVersion: "1") diff --git a/Example/Authenticator/Supporting Files/Localization/en.lproj/Authenticator.strings b/Example/Authenticator/Supporting Files/Localization/en.lproj/Authenticator.strings index 9d18b497..f1cd09b8 100644 --- a/Example/Authenticator/Supporting Files/Localization/en.lproj/Authenticator.strings +++ b/Example/Authenticator/Supporting Files/Localization/en.lproj/Authenticator.strings @@ -39,6 +39,7 @@ "actions.view_settings" = "View settings"; "actions.processing" = "Processing"; "actions.processing.description" = "This may take some time"; +"actions.access_to_location" = "Access to location"; "authorization.screen.name" = "Authenticator"; "authorization.active.title" = "Authorizing"; "authorization.success.title" = "Success"; @@ -58,6 +59,10 @@ "errors.contact_support" = "Something went wrong"; "errors.denied_camera" = "Camera access denied"; "errors.denied_camera_description" = "Please allow camera usage in phone settings. Without camera you won't be able to connect provider."; +"errors.turn_on_location_services" = "Turn on Location Services"; +"errors.turn_on_location_sharing_description" = "Select Location and tap “While Using the App” to allow Authenticator to determine your location, as requested by your service provider."; +"errors.turn_on_phone_location_services" = "Salt Edge Authenticator requires Location Services. To turn on Location Services, open the Settings app > select Privacy > select Location Services > enable Location Services"; +"errors.access_to_location_services" = "Access to Location Services"; "errors.inactive_connection" = "Inactive. Please reconnect."; "errors.no_active_connections" = "You have no currently connected providers."; "errors.no_suitable_connection" = "You have no suitable connection for this action"; @@ -68,6 +73,7 @@ "errors.passcode_ios_singular" = "You entered the wrong Passcode. Please try again in %{count} minute."; "errors.try_again" = "Please try again"; "errors.warning" = "Warning"; +"warnings.grant_access_to_location_services" = "Grant access to Location Services"; "warnings.inactivity_block_message" = "Application will be locked due to inactivity"; "in_app.connections_list.delete_all_connections" = "Delete all connections and stop receiving authorizations for all connections."; "in_app.connections_list.delete_connection" = "Are you sure you want to delete connection to your provider? This action cannot be undone."; diff --git a/Example/Authenticator/Utils/Extensions/ColorExtensions.swift b/Example/Authenticator/Utils/Extensions/ColorExtensions.swift index 1d556eaf..fb3cae79 100644 --- a/Example/Authenticator/Utils/Extensions/ColorExtensions.swift +++ b/Example/Authenticator/Utils/Extensions/ColorExtensions.swift @@ -90,4 +90,8 @@ extension UIColor { static var shadow: UIColor { return UIColor(named: "shadow", in: .authenticator_main, compatibleWith: nil)! } + + static var descriptionYellow: UIColor { + return UIColor(named: "descriptionYellow", in: .authenticator_main, compatibleWith: nil)! + } } diff --git a/Example/Authenticator/Utils/Helpers/Localizations.swift b/Example/Authenticator/Utils/Helpers/Localizations.swift index 1bba32ab..160cbf5c 100644 --- a/Example/Authenticator/Utils/Helpers/Localizations.swift +++ b/Example/Authenticator/Utils/Helpers/Localizations.swift @@ -43,6 +43,7 @@ enum Localizations: String, Localizable { case forgot = "actions.forgot" case remove = "actions.remove" case revoke = "actions.revoke" + case accessToLocation = "actions.access_to_location" // MARK: - Onboarding case getStarted = "actions.get_started" @@ -209,6 +210,12 @@ enum Localizations: String, Localizable { case wrongPasscode = "errors.wrong_passcode" case biometricsNotAvailable = "in_app.settings.biometrics_not_available" case biometricsNotAvailableDescription = "in_app.settings.biometrics_not_available_message" + case turnOnLocationServices = "errors.turn_on_location_services" + case turnOnLocationSharingDescription = "errors.turn_on_location_sharing_description" + case turnOnPhoneLocationServicesDescription = "errors.turn_on_phone_location_services" + + case accessToLocationServices = "errors.access_to_location_services" + case grantAccessToLocationServices = "warnings.grant_access_to_location_services" // MARK: - Connection Options case connect = "actions.connect" diff --git a/Example/Authenticator/Utils/Helpers/LocationManager.swift b/Example/Authenticator/Utils/Helpers/LocationManager.swift index 81ae262a..152704f4 100644 --- a/Example/Authenticator/Utils/Helpers/LocationManager.swift +++ b/Example/Authenticator/Utils/Helpers/LocationManager.swift @@ -26,7 +26,7 @@ import CoreLocation protocol LocationManagement { var notDeterminedAuthorization: Bool { get } var geolocationSharingIsEnabled: Bool { get } - func showLocationWarning(connection: Connection?) -> Bool + func shouldShowLocationWarning(connection: Connection?) -> Bool } final class LocationManager: NSObject, LocationManagement, CLLocationManagerDelegate { @@ -49,7 +49,11 @@ final class LocationManager: NSObject, LocationManagement, CLLocationManagerDele .contains(CLLocationManager.authorizationStatus()) } - func showLocationWarning(connection: Connection?) -> Bool { + var geolocationSharingIsDenied: Bool { + return CLLocationManager.authorizationStatus() == .denied + } + + func shouldShowLocationWarning(connection: Connection?) -> Bool { return (connection?.geolocationRequired.value ?? false) && !geolocationSharingIsEnabled } @@ -73,6 +77,7 @@ final class LocationManager: NSObject, LocationManagement, CLLocationManagerDele } func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { + NotificationsHelper.post(.locationServicesStatusDidChange) if status == .authorizedAlways || status == .authorizedWhenInUse { startUpdatingLocation() } diff --git a/Example/Authenticator/Utils/Helpers/NotificationsHelper.swift b/Example/Authenticator/Utils/Helpers/NotificationsHelper.swift index 741874e6..5b6b5ca5 100644 --- a/Example/Authenticator/Utils/Helpers/NotificationsHelper.swift +++ b/Example/Authenticator/Utils/Helpers/NotificationsHelper.swift @@ -25,6 +25,7 @@ import Foundation extension Notification.Name { static let networkConnectionIsNotReachable = Notification.Name("network-connection-is-not-reachable") static let networkConnectionIsReachable = Notification.Name("network-connection-is-reachable") + static let locationServicesStatusDidChange = Notification.Name("locationServicesStatusDidChange") } final class NotificationsHelper { diff --git a/Example/Authenticator/View Controllers/AuthorizationsViewController.swift b/Example/Authenticator/View Controllers/AuthorizationsViewController.swift index ad41deeb..5ea1b2c2 100644 --- a/Example/Authenticator/View Controllers/AuthorizationsViewController.swift +++ b/Example/Authenticator/View Controllers/AuthorizationsViewController.swift @@ -25,6 +25,7 @@ import UIKit protocol AuthorizationsViewControllerDelegate: class { func scanQrPressed() func showMoreOptionsMenu() + func requestLocation() } private struct Layout { @@ -98,12 +99,22 @@ final class AuthorizationsViewController: BaseViewController { message: l10n(.deniedCameraDescription), confirmActionTitle: l10n(.goToSettings), confirmActionStyle: .default, - confirmAction: { _ in - guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else { return } - - if UIApplication.shared.canOpenURL(settingsUrl) { - UIApplication.shared.open(settingsUrl) - } + confirmAction: { _ in strongSelf.openPhoneSettings() } + ) + } + case .requestLocationWarning: + if LocationManager.shared.geolocationSharingIsDenied { + strongSelf.showInfoAlert( + withTitle: l10n(.turnOnLocationServices), + message: l10n(.turnOnPhoneLocationServicesDescription) + ) + } else { + strongSelf.showConfirmationAlert( + withTitle: l10n(.accessToLocationServices), + message: l10n(.turnOnLocationSharingDescription), + confirmActionTitle: l10n(.goToSettings), + confirmActionStyle: .default, + confirmAction: { _ in strongSelf.openPhoneSettings() } ) } @@ -113,6 +124,14 @@ final class AuthorizationsViewController: BaseViewController { } } + private func openPhoneSettings() { + guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else { return } + + if UIApplication.shared.canOpenURL(settingsUrl) { + UIApplication.shared.open(settingsUrl) + } + } + private func setupNoDataView() { noDataView = NoDataView(data: viewModel.emptyViewData, action: scanQrPressed) } diff --git a/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift b/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift index 16921efd..8ce899c1 100644 --- a/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift +++ b/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift @@ -41,6 +41,7 @@ final class ConnectionsViewController: UITableViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) + reloadData() viewModel.refreshConsents() } @@ -53,12 +54,7 @@ final class ConnectionsViewController: UITableViewController { setupRefreshControl() layout() updateViewsHiddenState() - NotificationsHelper.observe( - self, - selector: #selector(reloadData), - name: NSLocale.currentLocaleDidChangeNotification, - object: nil - ) + setupObservers() } @objc private func reloadData() { @@ -86,6 +82,20 @@ final class ConnectionsViewController: UITableViewController { // MARK: - Setup private extension ConnectionsViewController { + func setupObservers() { + NotificationsHelper.observe( + self, + selector: #selector(reloadData), + name: NSLocale.currentLocaleDidChangeNotification, + object: nil + ) + NotificationsHelper.observe( + self, + selector: #selector(reloadData), + name: .locationServicesStatusDidChange, + object: nil + ) + } func setupTableView() { tableView.delegate = self tableView.dataSource = self @@ -190,6 +200,22 @@ extension ConnectionsViewController: ConnectionCellEventsDelegate { viewModel.updateName(by: id) } + func accessLocationPressed() { + showConfirmationAlert( + withTitle: l10n(.accessToLocationServices), + message: l10n(.turnOnLocationSharingDescription), + confirmActionTitle: l10n(.goToSettings), + confirmActionStyle: .default, + confirmAction: { _ in + guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else { return } + + if UIApplication.shared.canOpenURL(settingsUrl) { + UIApplication.shared.open(settingsUrl) + } + } + ) + } + func supportPressed(email: String) { viewModel.showSupport(email: email) } diff --git a/Example/Authenticator/View Models/Authorizations/AuthorizationDetailViewModel.swift b/Example/Authenticator/View Models/Authorizations/AuthorizationDetailViewModel.swift index 34f38e6e..5e83dfc0 100644 --- a/Example/Authenticator/View Models/Authorizations/AuthorizationDetailViewModel.swift +++ b/Example/Authenticator/View Models/Authorizations/AuthorizationDetailViewModel.swift @@ -85,6 +85,12 @@ final class AuthorizationDetailViewModel: Equatable { } } + var shouldShowLocationWarning: Bool { + guard let connection = ConnectionsCollector.active(by: connectionId) else { return false } + + return LocationManager.shared.shouldShowLocationWarning(connection: connection) + } + func confirmPressed() { delegate?.confirmPressed(authorizationId, apiVersion: apiVersion) } diff --git a/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift b/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift index bba5e52b..95cbec9b 100644 --- a/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift +++ b/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift @@ -30,10 +30,13 @@ enum AuthorizationsViewModelState: Equatable { case presentFail(String) case scanQrPressed(Bool) case normal + case requestLocationWarning static func == (lhs: AuthorizationsViewModelState, rhs: AuthorizationsViewModelState) -> Bool { switch (lhs, rhs) { - case (.changedConnectionsData, .changedConnectionsData), (.reloadData, .reloadData), (.normal, .normal): return true + case (.changedConnectionsData, .changedConnectionsData), + (.reloadData, .reloadData), (.normal, .normal), + (.requestLocationWarning, .requestLocationWarning): return true case let (.scrollTo(index1), .scrollTo(index2)): return index1 == index2 case let (.presentFail(message1), .presentFail(message2)): return message1 == message2 case let (.scanQrPressed(value1), .scanQrPressed(value2)): return value1 == value2 @@ -105,40 +108,48 @@ class AuthorizationsViewModel { guard let data = dataSource.confirmationData(for: authorizationId, apiVersion: apiVersion), let detailViewModel = dataSource.viewModel(with: authorizationId, apiVersion: apiVersion) else { return } - detailViewModel.state.value = .processing - - AuthorizationsInteractor.confirm( - apiVersion: detailViewModel.apiVersion, - data: data, - success: { - detailViewModel.state.value = .success - detailViewModel.actionTime = Date() - }, - failure: { _ in - detailViewModel.state.value = .undefined - detailViewModel.actionTime = Date() - } - ) + if detailViewModel.shouldShowLocationWarning { + state.value = .requestLocationWarning + } else { + detailViewModel.state.value = .processing + + AuthorizationsInteractor.confirm( + apiVersion: detailViewModel.apiVersion, + data: data, + success: { + detailViewModel.state.value = .success + detailViewModel.actionTime = Date() + }, + failure: { _ in + detailViewModel.state.value = .undefined + detailViewModel.actionTime = Date() + } + ) + } } func denyAuthorization(by authorizationId: String, apiVersion: ApiVersion) { guard let data = dataSource.confirmationData(for: authorizationId, apiVersion: apiVersion), let detailViewModel = dataSource.viewModel(with: authorizationId, apiVersion: apiVersion) else { return } - detailViewModel.state.value = .processing - - AuthorizationsInteractor.deny( - apiVersion: detailViewModel.apiVersion, - data: data, - success: { - detailViewModel.state.value = .denied - detailViewModel.actionTime = Date() - }, - failure: { _ in - detailViewModel.state.value = .undefined - detailViewModel.actionTime = Date() - } - ) + if detailViewModel.shouldShowLocationWarning { + state.value = .requestLocationWarning + } else { + detailViewModel.state.value = .processing + + AuthorizationsInteractor.deny( + apiVersion: detailViewModel.apiVersion, + data: data, + success: { + detailViewModel.state.value = .denied + detailViewModel.actionTime = Date() + }, + failure: { _ in + detailViewModel.state.value = .undefined + detailViewModel.actionTime = Date() + } + ) + } } private func updateDataSource(with authorizations: [SEBaseAuthorizationData]) { diff --git a/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift b/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift index 0fbcdb75..9b096367 100644 --- a/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift +++ b/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift @@ -42,7 +42,7 @@ final class SingleAuthorizationViewModel { getAuthorization( connection: connection, authorizationId: authorizationId, - showLocationWarning: locationManagement.showLocationWarning(connection: connection) + showLocationWarning: locationManagement.shouldShowLocationWarning(connection: connection) ) } diff --git a/Example/Authenticator/View Models/Connections/ConnectionCellViewModel.swift b/Example/Authenticator/View Models/Connections/ConnectionCellViewModel.swift index 1e57ec62..5c720da2 100644 --- a/Example/Authenticator/View Models/Connections/ConnectionCellViewModel.swift +++ b/Example/Authenticator/View Models/Connections/ConnectionCellViewModel.swift @@ -24,6 +24,7 @@ import UIKit protocol ConnectionCellEventsDelegate: class { func renamePressed(id: String) + func accessLocationPressed() func supportPressed(email: String) func deletePressed(id: String, showConfirmation: Bool) func reconnectPreseed(id: String) @@ -44,9 +45,16 @@ class ConnectionCellViewModel { } var description: NSAttributedString { - return connectionStatus.value == .inactive - ? buildInactiveDescription() - : buildActiveDescription() + if LocationManager.shared.shouldShowLocationWarning(connection: connection) { + return NSAttributedString( + string: l10n(.grantAccessToLocationServices), + attributes: [NSAttributedString.Key.foregroundColor: UIColor.descriptionYellow] + ) + } else { + return connectionStatus.value == .inactive + ? buildInactiveDescription() + : buildActiveDescription() + } } var hasConsents: Bool { @@ -92,6 +100,11 @@ class ConnectionCellViewModel { strongSelf.delegate?.consentsPressed(id: strongSelf.connection.id) }) } + if LocationManager.shared.shouldShowLocationWarning(connection: self?.connection) { + actions.append(UIAction(title: l10n(.accessToLocation), image: UIImage(systemName: "mappin")) { _ in + strongSelf.delegate?.accessLocationPressed() + }) + } if self?.connection.status == ConnectionStatus.inactive.rawValue { actions.append(UIAction(title: l10n(.remove), image: UIImage(systemName: "xmark")) { _ in diff --git a/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift b/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift index c82a2142..1d5c9633 100644 --- a/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift +++ b/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift @@ -27,8 +27,7 @@ import Nimble class AuthorizationsDataSourceSpec: BaseSpec { override func spec() { var firstModel, secondModel: AuthorizationDetailViewModel! - let mockLocationManager = MockLocationManager() - let dataSource = AuthorizationsDataSource(locationManagement: mockLocationManager) + let dataSource = AuthorizationsDataSource() var connection: Connection! beforeEach { @@ -121,7 +120,6 @@ class AuthorizationsDataSourceSpec: BaseSpec { "created_at": Date().iso8601string, "expires_at": Date().addingTimeInterval(3.0 * 60.0).iso8601string] let validDecryptedData = SpecUtils.createAuthResponse(with: validAuthMessage, id: connection.id, guid: connection.guid) - mockLocationManager.showLocationWarning = true _ = dataSource.update(with: [expiredDecryptedData, validDecryptedData]) diff --git a/Example/Tests/Spec Helpers/MockLocationManager.swift b/Example/Tests/Spec Helpers/MockLocationManager.swift index 09c796be..f738a5f0 100644 --- a/Example/Tests/Spec Helpers/MockLocationManager.swift +++ b/Example/Tests/Spec Helpers/MockLocationManager.swift @@ -29,7 +29,7 @@ class MockLocationManager: LocationManagement { var geolocationSharingIsEnabled: Bool = false - func showLocationWarning(connection: Connection?) -> Bool { + func shouldShowLocationWarning(connection: Connection?) -> Bool { return showLocationWarning } } diff --git a/Example/Tests/Spec Helpers/SpecUtils.swift b/Example/Tests/Spec Helpers/SpecUtils.swift index 284c9396..e7704e0a 100644 --- a/Example/Tests/Spec Helpers/SpecUtils.swift +++ b/Example/Tests/Spec Helpers/SpecUtils.swift @@ -26,11 +26,13 @@ import SEAuthenticatorV2 import SEAuthenticatorCore struct SpecUtils { - static func createConnection(id: ID, apiVersion: ApiVersion = "1") -> Connection { + static func createConnection(id: ID, apiVersion: ApiVersion = "1", geolocationRequired: Bool = true) -> Connection { let connection = Connection() connection.id = id connection.baseUrlString = "url.com" connection.apiVersion = apiVersion +// connection.status = "active" + connection.geolocationRequired.value = geolocationRequired ConnectionRepository.save(connection) _ = SECryptoHelper.createKeyPair(with: SETagHelper.create(for: connection.guid)) diff --git a/Example/Tests/ViewModels/AuthorizationsViewModelSpec.swift b/Example/Tests/ViewModels/AuthorizationsViewModelSpec.swift index 467676a5..9c5191bc 100644 --- a/Example/Tests/ViewModels/AuthorizationsViewModelSpec.swift +++ b/Example/Tests/ViewModels/AuthorizationsViewModelSpec.swift @@ -42,7 +42,7 @@ final class AuthorizationsViewModelSpec: BaseSpec { context("when there is no connections") { it("should return correct data") { let viewModel = AuthorizationsViewModel() - let dataSource = AuthorizationsDataSource(locationManagement: mockLocationManager) + let dataSource = AuthorizationsDataSource() viewModel.dataSource = dataSource expect(Array(ConnectionsCollector.allConnections)).to(beEmpty()) @@ -61,7 +61,7 @@ final class AuthorizationsViewModelSpec: BaseSpec { context("when there is at least one connection") { it("should retun correct data") { let viewModel = AuthorizationsViewModel() - let dataSource = AuthorizationsDataSource(locationManagement: mockLocationManager) + let dataSource = AuthorizationsDataSource() viewModel.dataSource = dataSource let expectedEmptyViewData = EmptyViewData( @@ -85,7 +85,7 @@ final class AuthorizationsViewModelSpec: BaseSpec { describe("confirmAuthorization") { it("should set state to .processing, then to completion state") { let viewModel = AuthorizationsViewModel() - let dataSource = AuthorizationsDataSource(locationManagement: mockLocationManager) + let dataSource = AuthorizationsDataSource() viewModel.dataSource = dataSource let connection = SpecUtils.createConnection(id: "123") @@ -99,6 +99,7 @@ final class AuthorizationsViewModelSpec: BaseSpec { let decryptedData = SpecUtils.createAuthResponse(with: authMessage, id: connection.id, guid: connection.guid) + mockLocationManager.geolocationSharingIsEnabled = true _ = dataSource.update(with: [decryptedData]) let detailViewModel = dataSource.viewModel(with: "00000", apiVersion: "1") @@ -108,12 +109,42 @@ final class AuthorizationsViewModelSpec: BaseSpec { expect(detailViewModel?.state.value).to(equal(AuthorizationStateView.AuthorizationState.processing)) expect(detailViewModel?.state.value).toEventually(equal(AuthorizationStateView.AuthorizationState.undefined)) } + + context("when location services are off and conection requires geolocation") { + it("should set state to .requestLocationWarning") { + let viewModel = AuthorizationsViewModel() + let dataSource = AuthorizationsDataSource() + viewModel.dataSource = dataSource + + let connection = SpecUtils.createConnection(id: "123") + + try? RealmManager.performRealmWriteTransaction { + connection.status = "active" + } + + let authMessage = ["id": "00000", + "connection_id": connection.id, + "title": "Authorization", + "description": "Test authorization", + "created_at": Date().iso8601string, + "expires_at": Date().addingTimeInterval(5.0 * 60.0).iso8601string] + + let decryptedData = SpecUtils.createAuthResponse(with: authMessage, id: connection.id, guid: connection.guid) + + mockLocationManager.geolocationSharingIsEnabled = false + _ = dataSource.update(with: [decryptedData]) + + viewModel.confirmAuthorization(by: "00000", apiVersion: "1") + + expect(viewModel.state.value).to(equal(AuthorizationsViewModelState.requestLocationWarning)) + } + } } describe("denyAuthorization") { it("should set state to .processing, then to completion state") { let viewModel = AuthorizationsViewModel() - let dataSource = AuthorizationsDataSource(locationManagement: mockLocationManager) + let dataSource = AuthorizationsDataSource() viewModel.dataSource = dataSource let connection = SpecUtils.createConnection(id: "123") @@ -127,6 +158,7 @@ final class AuthorizationsViewModelSpec: BaseSpec { let decryptedData = SpecUtils.createAuthResponse(with: authMessage, id: connection.id, guid: connection.guid) + mockLocationManager.geolocationSharingIsEnabled = true _ = dataSource.update(with: [decryptedData]) let detailViewModel = dataSource.viewModel(with: "00000", apiVersion: "1") @@ -136,6 +168,36 @@ final class AuthorizationsViewModelSpec: BaseSpec { expect(detailViewModel?.state.value).to(equal(AuthorizationStateView.AuthorizationState.processing)) expect(detailViewModel?.state.value).toEventually(equal(AuthorizationStateView.AuthorizationState.undefined)) } + + context("when location services are off and conection requires geolocation") { + it("should set state to .requestLocationWarning") { + let viewModel = AuthorizationsViewModel() + let dataSource = AuthorizationsDataSource() + viewModel.dataSource = dataSource + + let connection = SpecUtils.createConnection(id: "123") + + try? RealmManager.performRealmWriteTransaction { + connection.status = "active" + } + + let authMessage = ["id": "00000", + "connection_id": connection.id, + "title": "Authorization", + "description": "Test authorization", + "created_at": Date().iso8601string, + "expires_at": Date().addingTimeInterval(5.0 * 60.0).iso8601string] + + let decryptedData = SpecUtils.createAuthResponse(with: authMessage, id: connection.id, guid: connection.guid) + + mockLocationManager.geolocationSharingIsEnabled = false + _ = dataSource.update(with: [decryptedData]) + + viewModel.confirmAuthorization(by: "00000", apiVersion: "1") + + expect(viewModel.state.value).to(equal(AuthorizationsViewModelState.requestLocationWarning)) + } + } } } } From e13cc0c389203d6eff490ece5bde57de1f671f07 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Tue, 15 Jun 2021 11:19:28 +0300 Subject: [PATCH 032/110] cleared --- Example/Tests/Spec Helpers/SpecUtils.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Example/Tests/Spec Helpers/SpecUtils.swift b/Example/Tests/Spec Helpers/SpecUtils.swift index e7704e0a..77d74657 100644 --- a/Example/Tests/Spec Helpers/SpecUtils.swift +++ b/Example/Tests/Spec Helpers/SpecUtils.swift @@ -31,7 +31,6 @@ struct SpecUtils { connection.id = id connection.baseUrlString = "url.com" connection.apiVersion = apiVersion -// connection.status = "active" connection.geolocationRequired.value = geolocationRequired ConnectionRepository.save(connection) _ = SECryptoHelper.createKeyPair(with: SETagHelper.create(for: connection.guid)) From 3b34207227814af4dac5c18445479d23d264c6b8 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Thu, 17 Jun 2021 10:47:12 +0300 Subject: [PATCH 033/110] Fixed logic for instant action v2 --- .../Connect/InstantActionCoordinator.swift | 8 +- .../Connect/QRCodeCoordinator.swift | 7 +- .../Main/ApplicationCoordinator.swift | 9 +- .../Collectors/ConnectionsCollector.swift | 4 + .../Utils/Handlers/InstantActionHandler.swift | 121 ++++++++++++------ .../SingleAuthorizationViewModel.swift | 22 ++-- .../ConnectionPickerViewModel.swift | 23 +++- .../Collectors/ConnectionCollectorSpec.swift | 15 +++ .../API/Models/SEBaseActionResponse.swift | 13 +- .../Classes/API/SEConnectHelper.swift | 35 ++++- .../Classes/API/SENetKeys.swift | 8 ++ .../Classes/URLExtensions.swift | 2 +- .../Responses/SESubmitActionResponse.swift | 2 +- .../Classes/API/Helpers/ApiConstants.swift | 3 + .../API/Managers/SEActionManagerV2.swift | 49 +++++++ .../API/Models/SEActionRequestDataV2.swift | 50 ++++++++ .../Responses/SESubmitActionResponseV2.swift | 41 ++++++ .../API/Routers/SEActionRouterV2.swift | 60 +++++++++ 18 files changed, 397 insertions(+), 75 deletions(-) rename SaltedgeAuthenticatorSDK/Classes/Extensions/URLExtensions.swift => SaltedgeAuthenticatorCore/Classes/API/Models/SEBaseActionResponse.swift (73%) create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEActionManagerV2.swift create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEActionRequestDataV2.swift create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SESubmitActionResponseV2.swift create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEActionRouterV2.swift diff --git a/Example/Authenticator/Coordinators/Connect/InstantActionCoordinator.swift b/Example/Authenticator/Coordinators/Connect/InstantActionCoordinator.swift index 0a368072..4791daec 100644 --- a/Example/Authenticator/Coordinators/Connect/InstantActionCoordinator.swift +++ b/Example/Authenticator/Coordinators/Connect/InstantActionCoordinator.swift @@ -30,10 +30,10 @@ final class InstantActionCoordinator: Coordinator { private var instantActionHandler: InstantActionHandler private var qrCodeCoordinator: QRCodeCoordinator? - init(rootViewController: UIViewController, qrUrl: URL, actionGuid: GUID, connectUrl: URL) { + init(rootViewController: UIViewController, qrUrl: URL) { self.rootViewController = rootViewController self.connectViewController = ConnectViewController() - self.instantActionHandler = InstantActionHandler(qrUrl: qrUrl, actionGuid: actionGuid, connectUrl: connectUrl) + self.instantActionHandler = InstantActionHandler(qrUrl: qrUrl) if #available(iOS 13.0, *) { connectViewController.isModalInPresentation = true } @@ -65,10 +65,10 @@ extension InstantActionCoordinator: InstantActionEventsDelegate { } connectViewController.add(pickerViewController) - pickerViewModel.selectedConnectionClosure = { guid, accessToken in + pickerViewModel.selectedConnectionClosure = { actionData in pickerViewController.remove() self.connectViewController.title = l10n(.newAction) - self.instantActionHandler.submitAction(for: guid, accessToken: accessToken) + self.instantActionHandler.submitAction(for: actionData) } } diff --git a/Example/Authenticator/Coordinators/Connect/QRCodeCoordinator.swift b/Example/Authenticator/Coordinators/Connect/QRCodeCoordinator.swift index 1c1e35a8..486fe3b6 100644 --- a/Example/Authenticator/Coordinators/Connect/QRCodeCoordinator.swift +++ b/Example/Authenticator/Coordinators/Connect/QRCodeCoordinator.swift @@ -55,13 +55,10 @@ extension QRCodeCoordinator: QRCodeViewControllerDelegate { guard let url = URL(string: data), SEConnectHelper.isValid(deepLinkUrl: url) else { return } - if let actionGuid = SEConnectHelper.actionGuid(from: url), - let connectUrl = SEConnectHelper.connectUrl(from: url) { + if SEConnectHelper.shouldStartInstantActionFlow(url: url) { instantActionCoordinator = InstantActionCoordinator( rootViewController: rootViewController, - qrUrl: url, - actionGuid: actionGuid, - connectUrl: connectUrl + qrUrl: url ) instantActionCoordinator?.start() } else { diff --git a/Example/Authenticator/Coordinators/Main/ApplicationCoordinator.swift b/Example/Authenticator/Coordinators/Main/ApplicationCoordinator.swift index e461a286..deec350b 100644 --- a/Example/Authenticator/Coordinators/Main/ApplicationCoordinator.swift +++ b/Example/Authenticator/Coordinators/Main/ApplicationCoordinator.swift @@ -235,14 +235,11 @@ final class ApplicationCoordinator: Coordinator { } } - private func startConnect(url: URL, controller: UIViewController) { - if let actionGuid = SEConnectHelper.actionGuid(from: url), - let connectUrl = SEConnectHelper.connectUrl(from: url) { + private func startConnect(url: URL, controller: UIViewController) { + if SEConnectHelper.shouldStartInstantActionFlow(url: url) { instantActionCoordinator = InstantActionCoordinator( rootViewController: controller, - qrUrl: url, - actionGuid: actionGuid, - connectUrl: connectUrl + qrUrl: url ) instantActionCoordinator?.start() } else { diff --git a/Example/Authenticator/Models/Collectors/ConnectionsCollector.swift b/Example/Authenticator/Models/Collectors/ConnectionsCollector.swift index c85a6c14..46394533 100644 --- a/Example/Authenticator/Models/Collectors/ConnectionsCollector.swift +++ b/Example/Authenticator/Models/Collectors/ConnectionsCollector.swift @@ -45,6 +45,10 @@ struct ConnectionsCollector { return Array(activeConnections).filter { $0.baseUrl == connectUrl } } + static func activeConnections(by providerId: String) -> [Connection] { + return Array(activeConnections).filter { $0.providerId == providerId } + } + static var connectionNames: [String] { return allConnections.map { $0.name } } diff --git a/Example/Authenticator/Utils/Handlers/InstantActionHandler.swift b/Example/Authenticator/Utils/Handlers/InstantActionHandler.swift index cb71a364..5b679c22 100644 --- a/Example/Authenticator/Utils/Handlers/InstantActionHandler.swift +++ b/Example/Authenticator/Utils/Handlers/InstantActionHandler.swift @@ -22,6 +22,7 @@ import UIKit import SEAuthenticator +import SEAuthenticatorV2 import SEAuthenticatorCore protocol InstantActionEventsDelegate: class { @@ -34,68 +35,116 @@ protocol InstantActionEventsDelegate: class { final class InstantActionHandler { private var qrUrl: URL - private var actionGuid: GUID - private var connectUrl: URL weak var delegate: InstantActionEventsDelegate? - init(qrUrl: URL, actionGuid: GUID, connectUrl: URL) { + init(qrUrl: URL) { self.qrUrl = qrUrl - self.actionGuid = actionGuid - self.connectUrl = connectUrl + } + + private var actionGuid: GUID? { + return SEConnectHelper.actionGuid(from: qrUrl) + } + + private var connectUrl: URL? { + return SEConnectHelper.connectUrl(from: qrUrl) + } + + private var apiVersion: ApiVersion { + return SEConnectHelper.apiVersion(from: qrUrl) ?? "1" } func startHandling() { - handleQr(qrUrl: qrUrl, actionGuid: actionGuid, connectUrl: connectUrl) + handleQr(qrUrl: qrUrl) } - private func handleQr(qrUrl: URL, actionGuid: GUID, connectUrl: URL) { + func submitAction(for submitData: SubmitActionData) { + if apiVersion == "2" { + guard let providerId = qrUrl.queryItem(for: SENetKeys.providerId), + let actionId = qrUrl.queryItem(for: SENetKeys.actionId) else { return } + + let actionData = SEActionRequestDataV2( + url: submitData.baseUrl, + connectionGuid: submitData.connectionGuid, + accessToken: submitData.accessToken, + appLanguage: UserDefaultsHelper.applicationLanguage, + providerId: providerId, + actionId: actionId, + connectionId: submitData.connectionId + ) + + SEActionManagerV2.submitAction( + data: actionData, + onSuccess: { [weak self] response in + guard let strongSelf = self else { return } + + strongSelf.handleActionResponse(response, qrUrl: strongSelf.qrUrl) + }, + onFailure: { [weak self] _ in + DispatchQueue.main.async { + self?.delegate?.errorReceived(error: l10n(.actionError)) + } + } + ) + } else { + let actionData = SEActionRequestData( + url: connectUrl!, + connectionGuid: submitData.connectionGuid, + accessToken: submitData.accessToken, + appLanguage: UserDefaultsHelper.applicationLanguage, + guid: actionGuid! + ) + + SEActionManager.submitAction( + data: actionData, + onSuccess: { [weak self] response in + guard let strongSelf = self else { return } + + strongSelf.handleActionResponse(response, qrUrl: strongSelf.qrUrl) + }, + onFailure: { [weak self] _ in + DispatchQueue.main.async { + self?.delegate?.errorReceived(error: l10n(.actionError)) + } + } + ) + } + } + + private func handleQr(qrUrl: URL) { guard ConnectionsCollector.activeConnections.count > 0 else { self.delegate?.errorReceived(error: l10n(.noActiveConnection)) return } - let connections = ConnectionsCollector.activeConnections(by: connectUrl) + var connections = [Connection]() + + if apiVersion == "2", let providerId = SEConnectHelper.providerId(from: qrUrl) { + connections = ConnectionsCollector.activeConnections(by: providerId) + } else if let connectUrl = connectUrl { + connections = ConnectionsCollector.activeConnections(by: connectUrl) + } if connections.count > 1 { delegate?.shouldPresentConnectionPicker(connections: connections) } else if connections.count == 1 { - guard let connection = connections.first else { return } + guard let connection = connections.first, let baseUrl = connection.baseUrl else { return } submitAction( - for: connection.guid, - accessToken: connection.accessToken + for: SubmitActionData( + baseUrl: baseUrl, + connectionGuid: connection.guid, + connectionId: connection.id, + accessToken: connection.accessToken, + apiVersion: connection.apiVersion + ) ) } else { delegate?.shouldDismiss(with: l10n(.noSuitableConnection)) } } - func submitAction(for connectionGuid: GUID, accessToken: AccessToken) { - let actionData = SEActionRequestData( - url: connectUrl, - connectionGuid: connectionGuid, - accessToken: accessToken, - appLanguage: UserDefaultsHelper.applicationLanguage, - guid: actionGuid - ) - - SEActionManager.submitAction( - data: actionData, - onSuccess: { [weak self] response in - guard let strongSelf = self else { return } - - strongSelf.handleActionResponse(response, qrUrl: strongSelf.qrUrl) - }, - onFailure: { [weak self] _ in - DispatchQueue.main.async { - self?.delegate?.errorReceived(error: l10n(.actionError)) - } - } - ) - } - - private func handleActionResponse(_ response: SESubmitActionResponse, qrUrl: URL) { + private func handleActionResponse(_ response: SEBaseActionResponse, qrUrl: URL) { if let connectionId = response.connectionId, let authorizationId = response.authorizationId { delegate?.showAuthorization(connectionId: connectionId, authorizationId: authorizationId) diff --git a/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift b/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift index 9b096367..dd67b3e2 100644 --- a/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift +++ b/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift @@ -53,16 +53,22 @@ final class SingleAuthorizationViewModel { success: { [weak self] encryptedAuthorization in guard let strongSelf = self else { return } - DispatchQueue.global(qos: .background).async { - guard let decryptedAuthorizationData = encryptedAuthorization.decryptedAuthorizationData else { return } + var decryptedAuthorizationData: SEBaseAuthorizationData? - DispatchQueue.main.async { - if let viewModel = AuthorizationDetailViewModel(decryptedAuthorizationData, apiVersion: "1") { - strongSelf.detailViewModel = viewModel - strongSelf.detailViewModel?.delegate = self + if connection.isApiV2 { + decryptedAuthorizationData = encryptedAuthorization.decryptedAuthorizationDataV2 + } else { + decryptedAuthorizationData = encryptedAuthorization.decryptedAuthorizationData + } + + guard let data = decryptedAuthorizationData else { return } + + DispatchQueue.main.async { + if let viewModel = AuthorizationDetailViewModel(data, apiVersion: connection.apiVersion) { + strongSelf.detailViewModel = viewModel + strongSelf.detailViewModel?.delegate = self - strongSelf.delegate?.receivedDetailViewModel(viewModel) - } + strongSelf.delegate?.receivedDetailViewModel(viewModel) } } }, diff --git a/Example/Authenticator/View Models/Connections/ConnectionPickerViewModel.swift b/Example/Authenticator/View Models/Connections/ConnectionPickerViewModel.swift index ff18fdbe..417807a4 100644 --- a/Example/Authenticator/View Models/Connections/ConnectionPickerViewModel.swift +++ b/Example/Authenticator/View Models/Connections/ConnectionPickerViewModel.swift @@ -22,10 +22,18 @@ import UIKit +struct SubmitActionData { + var baseUrl: URL + var connectionGuid: String + var connectionId: String + var accessToken: String + var apiVersion: String +} + final class ConnectionPickerViewModel { private var connections: [Connection] - var selectedConnectionClosure: ((String, String) -> ())? + var selectedConnectionClosure: ((SubmitActionData) -> ())? init(connections: [Connection]) { self.connections = connections @@ -41,6 +49,17 @@ final class ConnectionPickerViewModel { func selectedConnection(at indexPath: IndexPath) { let viewModel = ConnectionCellViewModel(connection: connections[indexPath.row]) - selectedConnectionClosure?(viewModel.guid, viewModel.accessToken) + + guard let baseUrl = viewModel.baseUrl else { return } + + selectedConnectionClosure?( + SubmitActionData( + baseUrl: baseUrl, + connectionGuid: viewModel.guid, + connectionId: viewModel.id, + accessToken: viewModel.accessToken, + apiVersion: viewModel.apiVersion + ) + ) } } diff --git a/Example/Tests/Models/Collectors/ConnectionCollectorSpec.swift b/Example/Tests/Models/Collectors/ConnectionCollectorSpec.swift index 834868ed..2cba8b39 100644 --- a/Example/Tests/Models/Collectors/ConnectionCollectorSpec.swift +++ b/Example/Tests/Models/Collectors/ConnectionCollectorSpec.swift @@ -86,6 +86,21 @@ class ConnectionCollectorSpec: BaseSpec { } } + describe("activeConnections(by providerId)") { + it("should return array only of active connections filtered by connect url") { + let providerId = "1" + + let fifthConnection = Connection() + fifthConnection.id = "fifth" + fifthConnection.status = "active" + fifthConnection.providerId = providerId + + ConnectionRepository.save(fifthConnection) + + expect(Array(ConnectionsCollector.activeConnections(by: providerId))).to(equal([fifthConnection])) + } + } + describe("where:") { it("should properly serialize the arguments into the Object.filter call") { let whereString = "guid == '\(firstConnection.guid)'" diff --git a/SaltedgeAuthenticatorSDK/Classes/Extensions/URLExtensions.swift b/SaltedgeAuthenticatorCore/Classes/API/Models/SEBaseActionResponse.swift similarity index 73% rename from SaltedgeAuthenticatorSDK/Classes/Extensions/URLExtensions.swift rename to SaltedgeAuthenticatorCore/Classes/API/Models/SEBaseActionResponse.swift index 97cde44b..cc445f35 100644 --- a/SaltedgeAuthenticatorSDK/Classes/Extensions/URLExtensions.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/Models/SEBaseActionResponse.swift @@ -1,8 +1,8 @@ // -// URLExtensions.swift +// SEBaseActionResponse // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -22,10 +22,7 @@ import Foundation -extension URL { - func queryItem(for key: String) -> String? { - guard let components = URLComponents(url: self, resolvingAgainstBaseURL: false) else { return nil } - - return components.queryItems?.first(where: { $0.name == key })?.value - } +public protocol SEBaseActionResponse: SerializableResponse { + var authorizationId: String? { get } + var connectionId: String? { get } } diff --git a/SaltedgeAuthenticatorCore/Classes/API/SEConnectHelper.swift b/SaltedgeAuthenticatorCore/Classes/API/SEConnectHelper.swift index bb35a618..b7ba12ea 100644 --- a/SaltedgeAuthenticatorCore/Classes/API/SEConnectHelper.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/SEConnectHelper.swift @@ -28,23 +28,45 @@ public struct SEConnectHelper { } public static func isValidAction(deepLinkUrl url: URL) -> Bool { - return actionGuid(from: url) != nil && connectUrl(from: url) != nil + if let apiVersion = apiVersion(from: url), apiVersion == "2" { + return actionId(from: url) != nil && providerId(from: url) != nil + } else { + return actionGuid(from: url) != nil && connectUrl(from: url) != nil + } } public static func returnToUrl(from url: URL) -> URL? { - guard let query = url.queryItem(for: "return_to") else { return nil } + guard let query = url.queryItem(for: SENetKeys.returnTo) else { return nil } return URL(string: query) } public static func connectUrl(from url: URL) -> URL? { - guard let query = url.queryItem(for: "connect_url") else { return nil } + guard let query = url.queryItem(for: SENetKeys.connectUrl) else { return nil } return URL(string: query) } + public static func apiVersion(from url: URL) -> String? { + guard let query = url.queryItem(for: SENetKeys.apiVersion) else { return nil } + + return query + } + + public static func actionId(from url: URL) -> String? { + guard let query = url.queryItem(for: SENetKeys.actionId) else { return nil } + + return query + } + + public static func providerId(from url: URL) -> String? { + guard let query = url.queryItem(for: SENetKeys.providerId) else { return nil } + + return query + } + public static func actionGuid(from url: URL) -> String? { - guard let query = url.queryItem(for: "action_uuid") else { return nil } + guard let query = url.queryItem(for: SENetKeys.actionUuid) else { return nil } return query } @@ -60,5 +82,10 @@ public struct SEConnectHelper { return query } + + public static func shouldStartInstantActionFlow(url: URL) -> Bool { + return SEConnectHelper.actionId(from: url) != nil && SEConnectHelper.providerId(from: url) != nil || + SEConnectHelper.actionGuid(from: url) != nil && SEConnectHelper.connectUrl(from: url) != nil + } } diff --git a/SaltedgeAuthenticatorCore/Classes/API/SENetKeys.swift b/SaltedgeAuthenticatorCore/Classes/API/SENetKeys.swift index 6b7dc2af..de703f2a 100644 --- a/SaltedgeAuthenticatorCore/Classes/API/SENetKeys.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/SENetKeys.swift @@ -23,6 +23,8 @@ import Foundation public struct SENetKeys { + public static let apiVersion = "api_version" + public static let aps = "aps" public static let data = "data" @@ -51,6 +53,8 @@ public struct SENetKeys { public static let expiresAt = "expires_at" public static let redirectUrl = "redirect_url" + public static let returnTo = "return_to" + public static let key = "key" public static let iv = "iv" @@ -91,4 +95,8 @@ public struct SENetKeys { public static let device = "device" public static let location = "location" public static let ip = "ip" + + public static let actionId = "action_id" + public static let actionUuid = "action_uuid" + public static let providerId = "provider_id" } diff --git a/SaltedgeAuthenticatorCore/Classes/URLExtensions.swift b/SaltedgeAuthenticatorCore/Classes/URLExtensions.swift index 565ffade..03481413 100644 --- a/SaltedgeAuthenticatorCore/Classes/URLExtensions.swift +++ b/SaltedgeAuthenticatorCore/Classes/URLExtensions.swift @@ -22,7 +22,7 @@ import Foundation -extension URL { +public extension URL { func queryItem(for key: String) -> String? { guard let components = URLComponents(url: self, resolvingAgainstBaseURL: false) else { return nil } diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SESubmitActionResponse.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SESubmitActionResponse.swift index 65993e79..ac107a17 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SESubmitActionResponse.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SESubmitActionResponse.swift @@ -23,7 +23,7 @@ import Foundation import SEAuthenticatorCore -public struct SESubmitActionResponse: SerializableResponse { +public struct SESubmitActionResponse: SEBaseActionResponse { public let success: Bool public var authorizationId: String? public var connectionId: String? diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Helpers/ApiConstants.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Helpers/ApiConstants.swift index 05fc3233..370707f6 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Helpers/ApiConstants.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Helpers/ApiConstants.swift @@ -34,4 +34,7 @@ struct ApiConstants { static let authenticationUrl = "authentication_url" static let userAuthorizationType = "user_authorization_type" static let geolocation = "geolocation" + + static let actionId = "action_id" + static let connectionId = "connection_id" } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEActionManagerV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEActionManagerV2.swift new file mode 100644 index 00000000..812ea238 --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEActionManagerV2.swift @@ -0,0 +1,49 @@ +// +// SEActionManagerV2 +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +public struct SEActionManagerV2 { + public static func submitAction( + data: SEActionRequestDataV2, + onSuccess success: @escaping HTTPServiceSuccessClosure, + onFailure failure: @escaping FailureBlock + ) { + HTTPService.execute( + request: SEActionRouterV2.submit(data, parameters(requestData: data)), + success: success, + failure: failure + ) + } + + private static func parameters(requestData: SEActionRequestDataV2) -> [String: Any] { + return [ + SENetKeys.data: [ + ApiConstants.providerId: requestData.providerId, + ApiConstants.actionId: requestData.actionId, + ApiConstants.connectionId: requestData.connectionId + ], + ParametersKeys.exp: Date().addingTimeInterval(5.0 * 60.0).utcSeconds + ] + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEActionRequestDataV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEActionRequestDataV2.swift new file mode 100644 index 00000000..bf5d7621 --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEActionRequestDataV2.swift @@ -0,0 +1,50 @@ +// +// SEActionRequestDataV2 +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +public class SEActionRequestDataV2: SEBaseAuthenticatedRequestData { + public let providerId: String + public let actionId: String + public let connectionId: String + + public init( + url: URL, + connectionGuid: GUID, + accessToken: AccessToken, + appLanguage: ApplicationLanguage, + providerId: String, + actionId: String, + connectionId: String + ) { + self.providerId = providerId + self.actionId = actionId + self.connectionId = connectionId + super.init( + url: url, + connectionGuid: connectionGuid, + accessToken: accessToken, + appLanguage: appLanguage + ) + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SESubmitActionResponseV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SESubmitActionResponseV2.swift new file mode 100644 index 00000000..50810727 --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SESubmitActionResponseV2.swift @@ -0,0 +1,41 @@ +// +// SESubmitActionResponseV2 +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +public struct SESubmitActionResponseV2: SEBaseActionResponse { + public var authorizationId: String? + public var connectionId: String? + + public init?(_ value: Any) { + if let dict = value as? [String: Any], + let data = dict[SENetKeys.data] as? [String: Any], + let authorizationId = data[SENetKeys.authorizationId] as? String, + let connectionId = data[SENetKeys.connectionId] as? String { + self.authorizationId = authorizationId + self.connectionId = connectionId + } else { + return nil + } + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEActionRouterV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEActionRouterV2.swift new file mode 100644 index 00000000..2b8d11b3 --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEActionRouterV2.swift @@ -0,0 +1,60 @@ +// +// SEActionRouterV2 +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +enum SEActionRouterV2: Routable { + case submit(SEActionRequestDataV2, [String: Any]) + + var method: HTTPMethod { + return .post + } + + var encoding: Encoding { + return .json + } + + var url: URL { + switch self { + case .submit(let data, _): + return data.url.appendingPathComponent(SENetPathBuilder(for: .authorizations, version: 2).path) + } + } + + var headers: [String : String]? { + switch self { + case .submit(let data, let encryptedParameters): + return Headers.signedRequestHeaders( + token: data.accessToken, + payloadParams: encryptedParameters, + connectionGuid: data.connectionGuid + ) + } + } + + var parameters: [String : Any]? { + switch self { + case .submit(_, let encryptedParameters): return encryptedParameters + } + } +} From 7e3a549e45ff8125aff62c0c612e0a2729f4ee14 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Thu, 17 Jun 2021 17:00:33 +0300 Subject: [PATCH 034/110] cleared and added specs --- .../AuthorizationsViewModel.swift | 2 + .../Tests/Helpers/SEConnectHelperSpec.swift | 68 +++++++++++++++++++ .../RequestParametersBuilderSpecV2.swift | 28 ++++++++ .../API/Managers/SEActionManagerV2.swift | 13 +--- .../API/Routers/SEActionRouterV2.swift | 11 +-- .../Classes/RequestParametersBuilder.swift | 13 ++++ 6 files changed, 118 insertions(+), 17 deletions(-) diff --git a/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift b/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift index 95cbec9b..be6bbda8 100644 --- a/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift +++ b/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift @@ -274,6 +274,8 @@ private extension AuthorizationsViewModel { let decryptedAuthorizationsV1 = encryptedAuthorizations.compactMap { $0.decryptedAuthorizationData } let decryptedAuthorizationsV2 = encryptedAuthorizations.compactMap { $0.decryptedAuthorizationDataV2 } + print("Decrypted Authorizations V2: ", decryptedAuthorizationsV2) + DispatchQueue.main.async { strongSelf.updateDataSource(with: decryptedAuthorizationsV1 + decryptedAuthorizationsV2) } diff --git a/Example/Tests/Helpers/SEConnectHelperSpec.swift b/Example/Tests/Helpers/SEConnectHelperSpec.swift index edabd164..86b054f1 100644 --- a/Example/Tests/Helpers/SEConnectHelperSpec.swift +++ b/Example/Tests/Helpers/SEConnectHelperSpec.swift @@ -58,6 +58,40 @@ class SEConnectHelperSpec: BaseSpec { } } + describe("isValidAction") { + context("when api version is 2") { + it("should return true if url containts api_version, action_id and provider_id") { + let url = URL(string: "authenticator://saltedge.com/action?api_version=2&action_id=1&provider_id=1&return_to=http://return.com")! + + expect(SEConnectHelper.isValidAction(deepLinkUrl: url)).to(beTrue()) + } + } + + context("when url is missing one of the requirement fields") { + it("should return false") { + let url = URL(string: "authenticator://saltedge.com/action?api_version=2&action_id=1&return_to=http://return.com")! + + expect(SEConnectHelper.isValidAction(deepLinkUrl: url)).to(beFalse()) + } + } + + context("if it's api v1 and contains action_uuid and connect_url") { + it("should return true") { + let url = URL(string: "authenticator://saltedge.com/action?action_uuid=123456&connect_url=https://connect.com")! + + expect(SEConnectHelper.isValidAction(deepLinkUrl: url)).to(beTrue()) + } + } + + context("if it's api v1 and url is missing required fields") { + it("should return false") { + let url = URL(string: "authenticator://saltedge.com/action?action_uuid=123456")! + + expect(SEConnectHelper.isValidAction(deepLinkUrl: url)).to(beFalse()) + } + } + } + describe("actionGuid") { it("should return action_uuid param value or nil") { let expectUrl = URL(string: "authenticator://saltedge.com/action?action_uuid=123456") @@ -122,5 +156,39 @@ class SEConnectHelperSpec: BaseSpec { expect(SEConnectHelper.connectQuery(from: expectUrl!)).to(equal("1234567890")) } } + + describe("shouldStartInstantActionFlow") { + context("when it's v2 and contains action_id and provider_id") { + it("should return true") { + let url = URL(string: "authenticator://saltedge.com/action?api_version=2&action_id=1&provider_id=1&return_to=http://return.com")! + + expect(SEConnectHelper.shouldStartInstantActionFlow(url: url)).to(beTrue()) + } + } + + context("when it's v2 and url is missing one of the required fields") { + it("should return false") { + let url = URL(string: "authenticator://saltedge.com/action?api_version=2&provider_id=1&return_to=http://return.com")! + + expect(SEConnectHelper.shouldStartInstantActionFlow(url: url)).to(beFalse()) + } + } + + context("when it's v1 and contains action_uuid and connect_url") { + it("should return true") { + let url = URL(string: "authenticator://saltedge.com/action?action_uuid=123456&connect_url=https://connect.com")! + + expect(SEConnectHelper.shouldStartInstantActionFlow(url: url)).to(beTrue()) + } + } + + context("when it's v1 and url is missing one of the required fields") { + it("should return false") { + let url = URL(string: "authenticator://saltedge.com/action?connect_url=https://connect.com")! + + expect(SEConnectHelper.shouldStartInstantActionFlow(url: url)).to(beFalse()) + } + } + } } } diff --git a/Example/Tests/v2/Networking/RequestParametersBuilderSpecV2.swift b/Example/Tests/v2/Networking/RequestParametersBuilderSpecV2.swift index 18d9aa55..a32395bb 100644 --- a/Example/Tests/v2/Networking/RequestParametersBuilderSpecV2.swift +++ b/Example/Tests/v2/Networking/RequestParametersBuilderSpecV2.swift @@ -49,6 +49,34 @@ class RequestParametersBuilderSpecV2: BaseSpec { expect(result).to(beTruthy()) } } + + describe("actionParameters") { + it("should retun correct dict") { + let data = SEActionRequestDataV2( + url: URL(string: "https://url.com")!, + connectionGuid: "123", + accessToken: "456", + appLanguage: "en", + providerId: "1", + actionId: "99", + connectionId: "15" + ) + let expirationTime = Date().addingTimeInterval(5.0 * 60.0).utcSeconds + + let expectedParams: [String: Any] = [ + SENetKeys.data: [ + ParametersKeys.providerId: data.providerId, + ParametersKeys.actionId: data.actionId, + ParametersKeys.connectionId: data.connectionId + ], + ParametersKeys.exp: expirationTime + ] + + let result = RequestParametersBuilder.actionParameters(requestData: data) == expectedParams + + expect(result).to(beTruthy()) + } + } } } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEActionManagerV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEActionManagerV2.swift index 812ea238..7a1faa99 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEActionManagerV2.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEActionManagerV2.swift @@ -30,20 +30,9 @@ public struct SEActionManagerV2 { onFailure failure: @escaping FailureBlock ) { HTTPService.execute( - request: SEActionRouterV2.submit(data, parameters(requestData: data)), + request: SEActionRouterV2.submit(data), success: success, failure: failure ) } - - private static func parameters(requestData: SEActionRequestDataV2) -> [String: Any] { - return [ - SENetKeys.data: [ - ApiConstants.providerId: requestData.providerId, - ApiConstants.actionId: requestData.actionId, - ApiConstants.connectionId: requestData.connectionId - ], - ParametersKeys.exp: Date().addingTimeInterval(5.0 * 60.0).utcSeconds - ] - } } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEActionRouterV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEActionRouterV2.swift index 2b8d11b3..15bf99e7 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEActionRouterV2.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEActionRouterV2.swift @@ -24,7 +24,7 @@ import Foundation import SEAuthenticatorCore enum SEActionRouterV2: Routable { - case submit(SEActionRequestDataV2, [String: Any]) + case submit(SEActionRequestDataV2) var method: HTTPMethod { return .post @@ -36,17 +36,17 @@ enum SEActionRouterV2: Routable { var url: URL { switch self { - case .submit(let data, _): + case .submit(let data): return data.url.appendingPathComponent(SENetPathBuilder(for: .authorizations, version: 2).path) } } var headers: [String : String]? { switch self { - case .submit(let data, let encryptedParameters): + case .submit(let data): return Headers.signedRequestHeaders( token: data.accessToken, - payloadParams: encryptedParameters, + payloadParams: RequestParametersBuilder.actionParameters(requestData: data), connectionGuid: data.connectionGuid ) } @@ -54,7 +54,8 @@ enum SEActionRouterV2: Routable { var parameters: [String : Any]? { switch self { - case .submit(_, let encryptedParameters): return encryptedParameters + case .submit(let data): + return RequestParametersBuilder.actionParameters(requestData: data) } } } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift b/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift index a405711a..d9f0e74c 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift @@ -27,6 +27,8 @@ struct ParametersKeys { static let data = "data" static let key = "key" static let iv = "iv" + static let actionId = "action_id" + static let connectionId = "connection_id" static let providerId = "provider_id" static let publicKey = "public_key" static let deviceInfo = "device_info" @@ -80,4 +82,15 @@ struct RequestParametersBuilder { ParametersKeys.exp: exp ] } + + static func actionParameters(requestData: SEActionRequestDataV2) -> [String: Any] { + return [ + SENetKeys.data: [ + ParametersKeys.providerId: requestData.providerId, + ParametersKeys.actionId: requestData.actionId, + ParametersKeys.connectionId: requestData.connectionId + ], + ParametersKeys.exp: Date().addingTimeInterval(5.0 * 60.0).utcSeconds + ] + } } From 9d1c2188c7fe6c35e3e4239a5e927da24e06e1b8 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Fri, 18 Jun 2021 12:44:24 +0300 Subject: [PATCH 035/110] fixed cancel enrollment crash --- .../Coordinators/Connect/QRCodeCoordinator.swift | 3 --- .../Connection/ConnectViewController.swift | 10 +++++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Example/Authenticator/Coordinators/Connect/QRCodeCoordinator.swift b/Example/Authenticator/Coordinators/Connect/QRCodeCoordinator.swift index 486fe3b6..c0b0a9b1 100644 --- a/Example/Authenticator/Coordinators/Connect/QRCodeCoordinator.swift +++ b/Example/Authenticator/Coordinators/Connect/QRCodeCoordinator.swift @@ -29,15 +29,12 @@ final class QRCodeCoordinator: Coordinator { private var connectViewCoordinator: ConnectViewCoordinator? private var instantActionCoordinator: InstantActionCoordinator? - var dismissClosure: (() -> ())? - init(rootViewController: UIViewController) { self.rootViewController = rootViewController self.qrCodeViewController = QRCodeViewController() } func start() { - dismissClosure = qrCodeViewController.shouldDismissClosure qrCodeViewController.delegate = self if let navController = rootViewController.navigationController { navController.present(qrCodeViewController, animated: true) diff --git a/Example/Authenticator/View Controllers/Connection/ConnectViewController.swift b/Example/Authenticator/View Controllers/Connection/ConnectViewController.swift index 00602668..06675af7 100644 --- a/Example/Authenticator/View Controllers/Connection/ConnectViewController.swift +++ b/Example/Authenticator/View Controllers/Connection/ConnectViewController.swift @@ -39,7 +39,7 @@ final class ConnectViewController: BaseViewController { message: l10n(.pleaseTryAgain), actionTitle: l10n(.ok), completion: { - self.dismiss(animated: true) + self.cancelPressed() } ) return @@ -64,9 +64,13 @@ private extension ConnectViewController { title: l10n(.cancel), style: .plain, target: self, - action: #selector(close) + action: #selector(cancelPressed) ) } + + @objc func cancelPressed() { + dismiss(animated: true) + } } // MARK: - Actions @@ -88,6 +92,6 @@ extension ConnectViewController { // MARK: - CompleteViewDelegate extension ConnectViewController: CompleteViewDelegate { func proceedPressed(for view: CompleteView) { - dismiss(animated: true, completion: nil) + cancelPressed() } } From 2152acdaf0d6ff3153aa67bd1bc6861d259f02e4 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Fri, 18 Jun 2021 15:47:23 +0300 Subject: [PATCH 036/110] fixed authorization content --- .../AuthorizationContentDynamicStackView.swift | 3 ++- .../Authorizations/AuthorizationContentView.swift | 13 +++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Example/Authenticator/Views/Authorizations/AuthorizationContentDynamicStackView.swift b/Example/Authenticator/Views/Authorizations/AuthorizationContentDynamicStackView.swift index 396e2c08..570443bf 100644 --- a/Example/Authenticator/Views/Authorizations/AuthorizationContentDynamicStackView.swift +++ b/Example/Authenticator/Views/Authorizations/AuthorizationContentDynamicStackView.swift @@ -109,6 +109,7 @@ final class AuthorizationContentDynamicStackView: UIStackView { ) let descriptionLabel = UILabel( font: .systemFont(ofSize: 16.0, weight: .regular), + alignment: .right, textColor: fieldType == .payment ? .titleColor : FieldType.extra.textColor ) @@ -132,7 +133,7 @@ final class AuthorizationContentDynamicStackView: UIStackView { descriptionLabel.centerYToSuperview() if fieldType == .payment { - descriptionLabel.leftToRight(of: contentTitleLabel, offset: 32.0, relation: .equalOrGreater) + descriptionLabel.leftToRight(of: contentTitleLabel, offset: 16.0, relation: .equalOrGreater) descriptionLabel.rightToSuperview(offset: -16.0) } else { descriptionLabel.leftToRight(of: contentTitleLabel, offset: 6.0) diff --git a/Example/Authenticator/Views/Authorizations/AuthorizationContentView.swift b/Example/Authenticator/Views/Authorizations/AuthorizationContentView.swift index 382d8da4..20819efc 100644 --- a/Example/Authenticator/Views/Authorizations/AuthorizationContentView.swift +++ b/Example/Authenticator/Views/Authorizations/AuthorizationContentView.swift @@ -113,14 +113,15 @@ final class AuthorizationContentView: UIView { } else { contentStackView.removeAllArrangedSubviews() - let attributesView = UIView() - attributesView.addSubview(attributesStackView) - contentStackView.addArrangedSubview(attributesView) + let attributesScrollView = UIScrollView() + attributesScrollView.addSubview(attributesStackView) - attributesView.edgesToSuperview() + contentStackView.addArrangedSubview(attributesScrollView) - attributesStackView.topToSuperview() - attributesStackView.widthToSuperview() + attributesScrollView.edgesToSuperview() + + attributesStackView.width(to: attributesScrollView) + attributesStackView.edgesToSuperview() attributesStackView.setup(using: attributes) } From 4c544001ee0140d0c707a527f3e69552042ead76 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Fri, 18 Jun 2021 18:42:05 +0300 Subject: [PATCH 037/110] added user agent header --- .../Helpers/DeviceModelExtension.swift | 237 ++++++++++++++++++ .../Classes/Routers/Routable.swift | 14 ++ 2 files changed, 251 insertions(+) create mode 100644 SaltedgeAuthenticatorCore/Classes/Helpers/DeviceModelExtension.swift diff --git a/SaltedgeAuthenticatorCore/Classes/Helpers/DeviceModelExtension.swift b/SaltedgeAuthenticatorCore/Classes/Helpers/DeviceModelExtension.swift new file mode 100644 index 00000000..127ad91b --- /dev/null +++ b/SaltedgeAuthenticatorCore/Classes/Helpers/DeviceModelExtension.swift @@ -0,0 +1,237 @@ +// +// DeviceModelExtension +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import UIKit + +enum Model: String { + //Simulator + case simulator = "simulator/sandbox", + + //iPad + iPad2 = "iPad 2", + iPad3 = "iPad 3", + iPad4 = "iPad 4", + iPadAir = "iPad Air ", + iPadAir2 = "iPad Air 2", + iPadAir3 = "iPad Air 3", + iPadAir4 = "iPad Air 4", + iPad5 = "iPad 5", //iPad 2017 + iPad6 = "iPad 6", //iPad 2018 + iPad7 = "iPad 7", //iPad 2019 + iPad8 = "iPad 8", //iPad 2020 + + //iPad Mini + iPadMini = "iPad Mini", + iPadMini2 = "iPad Mini 2", + iPadMini3 = "iPad Mini 3", + iPadMini4 = "iPad Mini 4", + iPadMini5 = "iPad Mini 5", + + //iPad Pro + iPadPro9_7 = "iPad Pro 9.7\"", + iPadPro10_5 = "iPad Pro 10.5\"", + iPadPro11 = "iPad Pro 11\"", + iPadPro2_11 = "iPad Pro 11\" 2nd gen", + iPadPro3_11 = "iPad Pro 11\" 3nd gen", + iPadPro12_9 = "iPad Pro 12.9\"", + iPadPro2_12_9 = "iPad Pro 2 12.9\"", + iPadPro3_12_9 = "iPad Pro 3 12.9\"", + iPadPro4_12_9 = "iPad Pro 4 12.9\"", + iPadPro5_12_9 = "iPad Pro 5 12.9\"", + + //iPhone + iPhone4 = "iPhone 4", + iPhone4S = "iPhone 4S", + iPhone5 = "iPhone 5", + iPhone5S = "iPhone 5S", + iPhone5C = "iPhone 5C", + iPhone6 = "iPhone 6", + iPhone6Plus = "iPhone 6 Plus", + iPhone6S = "iPhone 6S", + iPhone6SPlus = "iPhone 6S Plus", + iPhoneSE = "iPhone SE", + iPhone7 = "iPhone 7", + iPhone7Plus = "iPhone 7 Plus", + iPhone8 = "iPhone 8", + iPhone8Plus = "iPhone 8 Plus", + iPhoneX = "iPhone X", + iPhoneXS = "iPhone XS", + iPhoneXSMax = "iPhone XS Max", + iPhoneXR = "iPhone XR", + iPhone11 = "iPhone 11", + iPhone11Pro = "iPhone 11 Pro", + iPhone11ProMax = "iPhone 11 Pro Max", + iPhoneSE2 = "iPhone SE 2nd gen", + iPhone12Mini = "iPhone 12 Mini", + iPhone12 = "iPhone 12", + iPhone12Pro = "iPhone 12 Pro", + iPhone12ProMax = "iPhone 12 Pro Max", + + unrecognized = "?unrecognized?" +} + +// MARK: UIDevice extensions +extension UIDevice { + var type: Model { + var systemInfo = utsname() + uname(&systemInfo) + let modelCode = withUnsafePointer(to: &systemInfo.machine) { + $0.withMemoryRebound(to: CChar.self, capacity: 1) { + ptr in String.init(validatingUTF8: ptr) + } + } + + let modelMap : [String: Model] = [ + //Simulator + "i386" : .simulator, + "x86_64" : .simulator, + + //iPad + "iPad2,1" : .iPad2, + "iPad2,2" : .iPad2, + "iPad2,3" : .iPad2, + "iPad2,4" : .iPad2, + "iPad3,1" : .iPad3, + "iPad3,2" : .iPad3, + "iPad3,3" : .iPad3, + "iPad3,4" : .iPad4, + "iPad3,5" : .iPad4, + "iPad3,6" : .iPad4, + "iPad6,11" : .iPad5, //iPad 2017 + "iPad6,12" : .iPad5, + "iPad7,5" : .iPad6, //iPad 2018 + "iPad7,6" : .iPad6, + "iPad7,11" : .iPad7, //iPad 2019 + "iPad7,12" : .iPad7, + "iPad11,6" : .iPad8, //iPad 2020 + "iPad11,7" : .iPad8, + + //iPad Mini + "iPad2,5" : .iPadMini, + "iPad2,6" : .iPadMini, + "iPad2,7" : .iPadMini, + "iPad4,4" : .iPadMini2, + "iPad4,5" : .iPadMini2, + "iPad4,6" : .iPadMini2, + "iPad4,7" : .iPadMini3, + "iPad4,8" : .iPadMini3, + "iPad4,9" : .iPadMini3, + "iPad5,1" : .iPadMini4, + "iPad5,2" : .iPadMini4, + "iPad11,1" : .iPadMini5, + "iPad11,2" : .iPadMini5, + + //iPad Pro + "iPad6,3" : .iPadPro9_7, + "iPad6,4" : .iPadPro9_7, + "iPad7,3" : .iPadPro10_5, + "iPad7,4" : .iPadPro10_5, + "iPad6,7" : .iPadPro12_9, + "iPad6,8" : .iPadPro12_9, + "iPad7,1" : .iPadPro2_12_9, + "iPad7,2" : .iPadPro2_12_9, + "iPad8,1" : .iPadPro11, + "iPad8,2" : .iPadPro11, + "iPad8,3" : .iPadPro11, + "iPad8,4" : .iPadPro11, + "iPad8,9" : .iPadPro2_11, + "iPad8,10" : .iPadPro2_11, + "iPad13,4" : .iPadPro3_11, + "iPad13,5" : .iPadPro3_11, + "iPad13,6" : .iPadPro3_11, + "iPad13,7" : .iPadPro3_11, + "iPad8,5" : .iPadPro3_12_9, + "iPad8,6" : .iPadPro3_12_9, + "iPad8,7" : .iPadPro3_12_9, + "iPad8,8" : .iPadPro3_12_9, + "iPad8,11" : .iPadPro4_12_9, + "iPad8,12" : .iPadPro4_12_9, + "iPad13,8" : .iPadPro5_12_9, + "iPad13,9" : .iPadPro5_12_9, + "iPad13,10" : .iPadPro5_12_9, + "iPad13,11" : .iPadPro5_12_9, + + //iPad Air + "iPad4,1" : .iPadAir, + "iPad4,2" : .iPadAir, + "iPad4,3" : .iPadAir, + "iPad5,3" : .iPadAir2, + "iPad5,4" : .iPadAir2, + "iPad11,3" : .iPadAir3, + "iPad11,4" : .iPadAir3, + "iPad13,1" : .iPadAir4, + "iPad13,2" : .iPadAir4, + + //iPhone + "iPhone3,1" : .iPhone4, + "iPhone3,2" : .iPhone4, + "iPhone3,3" : .iPhone4, + "iPhone4,1" : .iPhone4S, + "iPhone5,1" : .iPhone5, + "iPhone5,2" : .iPhone5, + "iPhone5,3" : .iPhone5C, + "iPhone5,4" : .iPhone5C, + "iPhone6,1" : .iPhone5S, + "iPhone6,2" : .iPhone5S, + "iPhone7,1" : .iPhone6Plus, + "iPhone7,2" : .iPhone6, + "iPhone8,1" : .iPhone6S, + "iPhone8,2" : .iPhone6SPlus, + "iPhone8,4" : .iPhoneSE, + "iPhone9,1" : .iPhone7, + "iPhone9,3" : .iPhone7, + "iPhone9,2" : .iPhone7Plus, + "iPhone9,4" : .iPhone7Plus, + "iPhone10,1" : .iPhone8, + "iPhone10,4" : .iPhone8, + "iPhone10,2" : .iPhone8Plus, + "iPhone10,5" : .iPhone8Plus, + "iPhone10,3" : .iPhoneX, + "iPhone10,6" : .iPhoneX, + "iPhone11,2" : .iPhoneXS, + "iPhone11,4" : .iPhoneXSMax, + "iPhone11,6" : .iPhoneXSMax, + "iPhone11,8" : .iPhoneXR, + "iPhone12,1" : .iPhone11, + "iPhone12,3" : .iPhone11Pro, + "iPhone12,5" : .iPhone11ProMax, + "iPhone12,8" : .iPhoneSE2, + "iPhone13,1" : .iPhone12Mini, + "iPhone13,2" : .iPhone12, + "iPhone13,3" : .iPhone12Pro, + "iPhone13,4" : .iPhone12ProMax + ] + + if let model = modelMap[String.init(validatingUTF8: modelCode!)!] { + if model == .simulator { + if let simModelCode = ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] { + if let simModel = modelMap[String.init(validatingUTF8: simModelCode)!] { + return simModel + } + } + } + return model + } + return Model.unrecognized + } +} diff --git a/SaltedgeAuthenticatorCore/Classes/Routers/Routable.swift b/SaltedgeAuthenticatorCore/Classes/Routers/Routable.swift index ae130924..d3e00d95 100644 --- a/SaltedgeAuthenticatorCore/Classes/Routers/Routable.swift +++ b/SaltedgeAuthenticatorCore/Classes/Routers/Routable.swift @@ -59,11 +59,25 @@ public extension Routable { if request.value(forHTTPHeaderField: "Content-Type") == nil { request.setValue("application/json", forHTTPHeaderField: "Content-Type") } + request.setValue(userAgentValue, forHTTPHeaderField: "User-Agent") request.httpBody = ParametersSerializer.createBody(parameters: parameters) return request } + + /* + Build application and device info: + e.g.: Authenticator / 3.3.0(130); (iPhone 8 Plus; iOS 14.5) + */ + private var userAgentValue: String { + let appName = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String ?? "" + let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "" + let buildNumber = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String ?? "" + + return "\(appName) / \(version)(\(buildNumber)); " + + "(\(UIDevice().type.rawValue); \(UIDevice.current.systemName) \(UIDevice.current.systemVersion))" + } } private extension Dictionary { From 21b6b7c9b9eb4e3c5887d9455b5e90cf974c7859 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Wed, 23 Jun 2021 11:46:01 +0300 Subject: [PATCH 038/110] fixed --- SaltedgeAuthenticatorCore/Classes/Routers/Routable.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SaltedgeAuthenticatorCore/Classes/Routers/Routable.swift b/SaltedgeAuthenticatorCore/Classes/Routers/Routable.swift index d3e00d95..0215197d 100644 --- a/SaltedgeAuthenticatorCore/Classes/Routers/Routable.swift +++ b/SaltedgeAuthenticatorCore/Classes/Routers/Routable.swift @@ -47,6 +47,7 @@ public extension Routable { var request = URLRequest(url: url) request.httpMethod = method.rawValue request.allHTTPHeaderFields = headers + request.setValue(userAgentValue, forHTTPHeaderField: "User-Agent") guard let parameters = parameters else { return request } @@ -59,7 +60,6 @@ public extension Routable { if request.value(forHTTPHeaderField: "Content-Type") == nil { request.setValue("application/json", forHTTPHeaderField: "Content-Type") } - request.setValue(userAgentValue, forHTTPHeaderField: "User-Agent") request.httpBody = ParametersSerializer.createBody(parameters: parameters) From 0c51334ab3642b27bf8c805e2c52a7a51143ff69 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Wed, 23 Jun 2021 12:08:41 +0300 Subject: [PATCH 039/110] cleared --- .../Classes/Helpers/DeviceModelExtension.swift | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/SaltedgeAuthenticatorCore/Classes/Helpers/DeviceModelExtension.swift b/SaltedgeAuthenticatorCore/Classes/Helpers/DeviceModelExtension.swift index 127ad91b..ae406a36 100644 --- a/SaltedgeAuthenticatorCore/Classes/Helpers/DeviceModelExtension.swift +++ b/SaltedgeAuthenticatorCore/Classes/Helpers/DeviceModelExtension.swift @@ -23,7 +23,7 @@ import Foundation import UIKit -enum Model: String { +enum DeviceModel: String { //Simulator case simulator = "simulator/sandbox", @@ -92,7 +92,7 @@ enum Model: String { // MARK: UIDevice extensions extension UIDevice { - var type: Model { + var type: DeviceModel { var systemInfo = utsname() uname(&systemInfo) let modelCode = withUnsafePointer(to: &systemInfo.machine) { @@ -101,7 +101,7 @@ extension UIDevice { } } - let modelMap : [String: Model] = [ + let modelMap : [String: DeviceModel] = [ //Simulator "i386" : .simulator, "x86_64" : .simulator, @@ -223,15 +223,9 @@ extension UIDevice { ] if let model = modelMap[String.init(validatingUTF8: modelCode!)!] { - if model == .simulator { - if let simModelCode = ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] { - if let simModel = modelMap[String.init(validatingUTF8: simModelCode)!] { - return simModel - } - } - } return model } - return Model.unrecognized + + return DeviceModel.unrecognized } } From 98480f7405c7d05de13fb03f5ab9d74d3054f1cf Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Wed, 23 Jun 2021 12:19:39 +0300 Subject: [PATCH 040/110] added specs --- Example/Tests/Networking/Routers/v1/ActionRouterSpec.swift | 4 +++- Example/Tests/Spec Helpers/URLRequestBuilder.swift | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Example/Tests/Networking/Routers/v1/ActionRouterSpec.swift b/Example/Tests/Networking/Routers/v1/ActionRouterSpec.swift index 3c08e416..4cbf859d 100644 --- a/Example/Tests/Networking/Routers/v1/ActionRouterSpec.swift +++ b/Example/Tests/Networking/Routers/v1/ActionRouterSpec.swift @@ -67,8 +67,10 @@ class ActionRouterSpec: BaseSpec { let request = SEActionRouter.submit(expectedActionData).asURLRequest() expect(request).to(equal(expectedRequest)) + + expect(request.allHTTPHeaderFields!["User-Agent"]) + .to(equal("TestHost-iOS / 1.0(1); (simulator/sandbox; iOS \(UIDevice.current.systemVersion))")) } } } } - diff --git a/Example/Tests/Spec Helpers/URLRequestBuilder.swift b/Example/Tests/Spec Helpers/URLRequestBuilder.swift index 89a6a83c..6e15eaf4 100644 --- a/Example/Tests/Spec Helpers/URLRequestBuilder.swift +++ b/Example/Tests/Spec Helpers/URLRequestBuilder.swift @@ -34,6 +34,10 @@ struct URLRequestBuilder { var request = URLRequest(url: url) request.httpMethod = method request.allHTTPHeaderFields = headers + request.setValue( + "TestHost-iOS / 1.0(1); (simulator/sandbox; iOS \(UIDevice.current.systemVersion))", + forHTTPHeaderField: "User-Agent" + ) if encoding == .url { var components = URLComponents(url: url, resolvingAgainstBaseURL: true) From c6db3584ebe957ad8346ce128f89abde3c3d733e Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Mon, 28 Jun 2021 14:21:20 +0300 Subject: [PATCH 041/110] fixed geolocation warning --- .../Coordinators/ConnectViewCoordinator.swift | 12 ++++++- .../Utils/Handlers/ConnectHandler.swift | 2 +- .../AuthorizationsViewController.swift | 35 +++++++++++-------- .../ConnectionsViewController.swift | 28 ++++++++------- .../Connections/ConnectionCellViewModel.swift | 32 ++++++++++------- 5 files changed, 68 insertions(+), 41 deletions(-) diff --git a/Example/Authenticator/Coordinators/ConnectViewCoordinator.swift b/Example/Authenticator/Coordinators/ConnectViewCoordinator.swift index 345d7fcc..a1c943ff 100644 --- a/Example/Authenticator/Coordinators/ConnectViewCoordinator.swift +++ b/Example/Authenticator/Coordinators/ConnectViewCoordinator.swift @@ -87,7 +87,17 @@ extension ConnectViewCoordinator: ConnectEventsDelegate { } func requestLocationAuthorization() { - LocationManager.shared.requestLocationAuthorization() + if LocationManager.shared.notDeterminedAuthorization { + LocationManager.shared.requestLocationAuthorization() + } else { + connectViewController.showInfoAlert( + withTitle: l10n(.turnOnLocationServices), + message: l10n(.turnOnPhoneLocationServicesDescription), + completion: { + LocationManager.shared.requestLocationAuthorization() + } + ) + } } func startWebViewLoading(with connectUrlString: String) { diff --git a/Example/Authenticator/Utils/Handlers/ConnectHandler.swift b/Example/Authenticator/Utils/Handlers/ConnectHandler.swift index 16117aa3..c5d0be7a 100644 --- a/Example/Authenticator/Utils/Handlers/ConnectHandler.swift +++ b/Example/Authenticator/Utils/Handlers/ConnectHandler.swift @@ -71,7 +71,7 @@ final class ConnectHandler { finalString.append(description) delegate?.finishConnectWithSuccess(attributedMessage: finalString) - if connection.geolocationRequired.value != nil && LocationManager.shared.notDeterminedAuthorization { + if connection.geolocationRequired.value != nil { delegate?.requestLocationAuthorization() } } diff --git a/Example/Authenticator/View Controllers/AuthorizationsViewController.swift b/Example/Authenticator/View Controllers/AuthorizationsViewController.swift index 5ea1b2c2..ebd01830 100644 --- a/Example/Authenticator/View Controllers/AuthorizationsViewController.swift +++ b/Example/Authenticator/View Controllers/AuthorizationsViewController.swift @@ -103,27 +103,32 @@ final class AuthorizationsViewController: BaseViewController { ) } case .requestLocationWarning: - if LocationManager.shared.geolocationSharingIsDenied { - strongSelf.showInfoAlert( - withTitle: l10n(.turnOnLocationServices), - message: l10n(.turnOnPhoneLocationServicesDescription) - ) - } else { - strongSelf.showConfirmationAlert( - withTitle: l10n(.accessToLocationServices), - message: l10n(.turnOnLocationSharingDescription), - confirmActionTitle: l10n(.goToSettings), - confirmActionStyle: .default, - confirmAction: { _ in strongSelf.openPhoneSettings() - } - ) - } + strongSelf.checkLocationServicesStatus() default: break } strongSelf.viewModel.resetState() } } + private func checkLocationServicesStatus() { + if LocationManager.shared.notDeterminedAuthorization { + LocationManager.shared.requestLocationAuthorization() + } else if LocationManager.shared.geolocationSharingIsDenied { + showConfirmationAlert( + withTitle: l10n(.accessToLocationServices), + message: l10n(.turnOnLocationSharingDescription), + confirmActionTitle: l10n(.goToSettings), + confirmActionStyle: .default, + confirmAction: { _ in self.openPhoneSettings() } + ) + } else { + showInfoAlert( + withTitle: l10n(.turnOnLocationServices), + message: l10n(.turnOnPhoneLocationServicesDescription) + ) + } + } + private func openPhoneSettings() { guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else { return } diff --git a/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift b/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift index 8ce899c1..17bf8c04 100644 --- a/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift +++ b/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift @@ -201,19 +201,23 @@ extension ConnectionsViewController: ConnectionCellEventsDelegate { } func accessLocationPressed() { - showConfirmationAlert( - withTitle: l10n(.accessToLocationServices), - message: l10n(.turnOnLocationSharingDescription), - confirmActionTitle: l10n(.goToSettings), - confirmActionStyle: .default, - confirmAction: { _ in - guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else { return } - - if UIApplication.shared.canOpenURL(settingsUrl) { - UIApplication.shared.open(settingsUrl) + if LocationManager.shared.notDeterminedAuthorization { + LocationManager.shared.requestLocationAuthorization() + } else { + showConfirmationAlert( + withTitle: l10n(.accessToLocationServices), + message: l10n(.turnOnLocationSharingDescription), + confirmActionTitle: l10n(.goToSettings), + confirmActionStyle: .default, + confirmAction: { _ in + guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else { return } + + if UIApplication.shared.canOpenURL(settingsUrl) { + UIApplication.shared.open(settingsUrl) + } } - } - ) + ) + } } func supportPressed(email: String) { diff --git a/Example/Authenticator/View Models/Connections/ConnectionCellViewModel.swift b/Example/Authenticator/View Models/Connections/ConnectionCellViewModel.swift index 5c720da2..d1ce1da0 100644 --- a/Example/Authenticator/View Models/Connections/ConnectionCellViewModel.swift +++ b/Example/Authenticator/View Models/Connections/ConnectionCellViewModel.swift @@ -96,24 +96,32 @@ class ConnectionCellViewModel { }) if self?.hasConsents == true { - actions.append(UIAction(title: l10n(.viewConsents), image: UIImage(systemName: "doc.plaintext")) { _ in - strongSelf.delegate?.consentsPressed(id: strongSelf.connection.id) - }) + actions.append( + UIAction(title: l10n(.viewConsents), image: UIImage(systemName: "doc.plaintext")) { _ in + strongSelf.delegate?.consentsPressed(id: strongSelf.connection.id) + } + ) } if LocationManager.shared.shouldShowLocationWarning(connection: self?.connection) { - actions.append(UIAction(title: l10n(.accessToLocation), image: UIImage(systemName: "mappin")) { _ in - strongSelf.delegate?.accessLocationPressed() - }) + actions.append( + UIAction(title: l10n(.accessToLocation), image: UIImage(systemName: "mappin.and.ellipse")) { _ in + strongSelf.delegate?.accessLocationPressed() + } + ) } if self?.connection.status == ConnectionStatus.inactive.rawValue { - actions.append(UIAction(title: l10n(.remove), image: UIImage(systemName: "xmark")) { _ in - strongSelf.delegate?.deletePressed(id: strongSelf.connection.id, showConfirmation: false) - }) + actions.append( + UIAction(title: l10n(.remove), image: UIImage(systemName: "xmark")) { _ in + strongSelf.delegate?.deletePressed(id: strongSelf.connection.id, showConfirmation: false) + } + ) } else { - actions.append(UIAction(title: l10n(.delete), image: UIImage(systemName: "trash")) { _ in - strongSelf.delegate?.deletePressed(id: strongSelf.connection.id, showConfirmation: true) - }) + actions.append( + UIAction(title: l10n(.delete), image: UIImage(systemName: "trash")) { _ in + strongSelf.delegate?.deletePressed(id: strongSelf.connection.id, showConfirmation: true) + } + ) } return UIMenu(title: "", image: nil, identifier: nil, options: .destructive, children: actions) From 11586cdf211dbde7027180802a7f7b90cfc8c2dd Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Wed, 7 Jul 2021 17:18:31 +0300 Subject: [PATCH 042/110] added final status handling and merging for v2 --- .../AuthorizationsDataSource.swift | 22 +++++-- .../SEEncryptedDataExtensions.swift | 17 ++++-- .../v1/AuthorizationsInteractor.swift | 20 +++++-- .../AuthorizationDetailViewModel.swift | 13 +++- .../AuthorizationsViewModel.swift | 22 +++---- .../SingleAuthorizationViewModel.swift | 30 ++++++---- .../AuthorizationContentView.swift | 4 +- .../AuthorizationStateView.swift | 29 +++++---- .../AuthorizationsDataSourceSpec.swift | 53 +++++++++------- .../ConfirmAuthorizationResponseSpec.swift | 4 +- Example/Tests/Spec Helpers/SpecUtils.swift | 19 ++++++ .../AuthorizationsViewModelSpec.swift | 4 +- .../SEConfirmAuthorizationResponse.swift | 40 +++++++++++++ .../Classes/Helpers/AuthorizationStatus.swift | 60 +++++++++++++++++++ .../Responses/AuthorizationResponses.swift | 17 ------ .../API/Models/SEAuthorizationDataV2.swift | 14 +++-- .../Responses/AuthorizationResponses.swift | 17 ------ 17 files changed, 269 insertions(+), 116 deletions(-) create mode 100644 SaltedgeAuthenticatorCore/Classes/API/Models/SEConfirmAuthorizationResponse.swift create mode 100644 SaltedgeAuthenticatorCore/Classes/Helpers/AuthorizationStatus.swift diff --git a/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift b/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift index 54e882e2..1f7d0509 100644 --- a/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift +++ b/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift @@ -29,10 +29,10 @@ final class AuthorizationsDataSource { private var viewModels = [AuthorizationDetailViewModel]() func update(with baseData: [SEBaseAuthorizationData]) -> Bool { - let viewModelsV1 = baseData.toAuthorizationViewModel(apiVersion: "1") + let viewModelsV1 = baseData.toAuthorizationViewModels(apiVersion: "1", oldViewModels: viewModels) .merge(array: self.viewModels.filter { $0.apiVersion == "1" }) - let viewModelsV2 = baseData.toAuthorizationViewModel(apiVersion: "2") + let viewModelsV2 = baseData.toAuthorizationViewModels(apiVersion: "2", oldViewModels: viewModels) .merge(array: self.viewModels.filter { $0.apiVersion == "2" }) let allViewModels = (viewModelsV1 + viewModelsV2).sorted(by: { $0.createdAt < $1.createdAt }) @@ -124,11 +124,23 @@ final class AuthorizationsDataSource { } private extension Array where Element == SEBaseAuthorizationData { - func toAuthorizationViewModel(apiVersion: ApiVersion) -> [AuthorizationDetailViewModel] { + func toAuthorizationViewModels( + apiVersion: ApiVersion, + oldViewModels: [AuthorizationDetailViewModel] + ) -> [AuthorizationDetailViewModel] { return filter { $0.apiVersion == apiVersion }.compactMap { response in - guard response.expiresAt >= Date() else { return nil } + if let v2Response = response as? SEAuthorizationDataV2, + v2Response.status.isFinalStatus, + let filtered = oldViewModels.filter({ $0.authorizationId == v2Response.id }).first { + guard filtered.authorizationExpiresAt >= Date(), !filtered.status.isFinalStatus else { return nil } - return AuthorizationDetailViewModel(response, apiVersion: response.apiVersion) + filtered.setFinal(status: v2Response.status) + return filtered + } else { + guard response.expiresAt >= Date() else { return nil } + + return AuthorizationDetailViewModel(response, apiVersion: response.apiVersion) + } } } } diff --git a/Example/Authenticator/Utils/Extensions/SEEncryptedDataExtensions.swift b/Example/Authenticator/Utils/Extensions/SEEncryptedDataExtensions.swift index 910380e1..755bfb97 100644 --- a/Example/Authenticator/Utils/Extensions/SEEncryptedDataExtensions.swift +++ b/Example/Authenticator/Utils/Extensions/SEEncryptedDataExtensions.swift @@ -34,15 +34,24 @@ extension SEBaseEncryptedAuthorizationData { } var decryptedAuthorizationDataV2: SEAuthorizationDataV2? { - if let decryptedDictionary = self.decryptedDictionary, - let v2Response = self as? SEEncryptedAuthorizationData, - let connectionId = connectionId { + guard let v2Response = self as? SEEncryptedAuthorizationData, + let connectionId = connectionId else { return nil } + + if v2Response.status.isFinalStatus, !v2Response.status.isProcessing { return SEAuthorizationDataV2( - decryptedDictionary, id: v2Response.id, connectionId: connectionId, status: v2Response.status ) + } else { + if let decryptedDictionary = self.decryptedDictionary { + return SEAuthorizationDataV2( + decryptedDictionary, + id: v2Response.id, + connectionId: connectionId, + status: v2Response.status + ) + } } return nil } diff --git a/Example/Authenticator/Utils/Interactors/v1/AuthorizationsInteractor.swift b/Example/Authenticator/Utils/Interactors/v1/AuthorizationsInteractor.swift index e4b41d5e..5bfc19db 100644 --- a/Example/Authenticator/Utils/Interactors/v1/AuthorizationsInteractor.swift +++ b/Example/Authenticator/Utils/Interactors/v1/AuthorizationsInteractor.swift @@ -29,19 +29,23 @@ struct AuthorizationsInteractor { static func confirm( apiVersion: ApiVersion, data: SEConfirmAuthorizationRequestData, - success: (() -> ())? = nil, + success: ((SEConfirmAuthorizationResponse) -> ())? = nil, failure: ((String) -> ())? = nil ) { if apiVersion == "2" { SEAuthorizationManagerV2.confirmAuthorization( data: data, - onSuccess: { _ in success?() }, + onSuccess: { response in + success?(response) + }, onFailure: { error in failure?(error) } ) } else { SEAuthorizationManager.confirmAuthorization( data: data, - onSuccess: { _ in success?() }, + onSuccess: { response in + success?(response) + }, onFailure: { error in failure?(error) } ) } @@ -50,19 +54,23 @@ struct AuthorizationsInteractor { static func deny( apiVersion: ApiVersion, data: SEConfirmAuthorizationRequestData, - success: (() -> ())? = nil, + success: ((SEConfirmAuthorizationResponse) -> ())? = nil, failure: ((String) -> ())? = nil ) { if apiVersion == "2" { SEAuthorizationManagerV2.denyAuthorization( data: data, - onSuccess: { _ in success?() }, + onSuccess: { response in + success?(response) + }, onFailure: { error in failure?(error) } ) } else { SEAuthorizationManager.denyAuthorization( data: data, - onSuccess: { _ in success?() }, + onSuccess: { response in + success?(response) + }, onFailure: { error in failure?(error) } ) } diff --git a/Example/Authenticator/View Models/Authorizations/AuthorizationDetailViewModel.swift b/Example/Authenticator/View Models/Authorizations/AuthorizationDetailViewModel.swift index 5e83dfc0..6be5a54e 100644 --- a/Example/Authenticator/View Models/Authorizations/AuthorizationDetailViewModel.swift +++ b/Example/Authenticator/View Models/Authorizations/AuthorizationDetailViewModel.swift @@ -36,6 +36,7 @@ final class AuthorizationDetailViewModel: Equatable { var authorizationId: String var connectionId: String var description: String = "" + var status: String = "" var descriptionAttributes: [String: Any] = [:] var authorizationCode: String? var lifetime: Int = 0 @@ -57,6 +58,7 @@ final class AuthorizationDetailViewModel: Equatable { } else if let dataV2 = data as? SEAuthorizationDataV2 { self.title = dataV2.title self.descriptionAttributes = dataV2.description + self.status = dataV2.status } self.apiVersion = apiVersion self.authorizationId = data.id @@ -65,7 +67,7 @@ final class AuthorizationDetailViewModel: Equatable { self.authorizationExpiresAt = data.expiresAt self.lifetime = Int(data.expiresAt.timeIntervalSince(data.createdAt)) self.createdAt = data.createdAt - self.state.value = data.expiresAt < Date() ? .expired : .base + self.state.value = data.expiresAt < Date() ? .timeOut : .base } static func == (lhs: AuthorizationDetailViewModel, rhs: AuthorizationDetailViewModel) -> Bool { @@ -98,4 +100,13 @@ final class AuthorizationDetailViewModel: Equatable { func denyPressed() { delegate?.denyPressed(authorizationId, apiVersion: apiVersion) } + + func setFinal(status: String) { + guard status.isFinalStatus, + let authStatus = AuthorizationStateView.AuthorizationState(rawValue: status) else { return } + + self.status = status + self.state.value = authStatus + self.actionTime = Date() + } } diff --git a/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift b/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift index be6bbda8..04083043 100644 --- a/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift +++ b/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift @@ -116,12 +116,14 @@ class AuthorizationsViewModel { AuthorizationsInteractor.confirm( apiVersion: detailViewModel.apiVersion, data: data, - success: { - detailViewModel.state.value = .success - detailViewModel.actionTime = Date() + success: { response in + if response.status.isFinalStatus { + detailViewModel.state.value = .confirmed + detailViewModel.actionTime = Date() + } }, failure: { _ in - detailViewModel.state.value = .undefined + detailViewModel.state.value = .error detailViewModel.actionTime = Date() } ) @@ -140,12 +142,14 @@ class AuthorizationsViewModel { AuthorizationsInteractor.deny( apiVersion: detailViewModel.apiVersion, data: data, - success: { - detailViewModel.state.value = .denied - detailViewModel.actionTime = Date() + success: { response in + if response.status.isFinalStatus { + detailViewModel.state.value = .denied + detailViewModel.actionTime = Date() + } }, failure: { _ in - detailViewModel.state.value = .undefined + detailViewModel.state.value = .error detailViewModel.actionTime = Date() } ) @@ -274,8 +278,6 @@ private extension AuthorizationsViewModel { let decryptedAuthorizationsV1 = encryptedAuthorizations.compactMap { $0.decryptedAuthorizationData } let decryptedAuthorizationsV2 = encryptedAuthorizations.compactMap { $0.decryptedAuthorizationDataV2 } - print("Decrypted Authorizations V2: ", decryptedAuthorizationsV2) - DispatchQueue.main.async { strongSelf.updateDataSource(with: decryptedAuthorizationsV1 + decryptedAuthorizationsV2) } diff --git a/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift b/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift index dd67b3e2..77ad1b7e 100644 --- a/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift +++ b/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift @@ -105,15 +105,17 @@ extension SingleAuthorizationViewModel: AuthorizationDetailEventsDelegate { AuthorizationsInteractor.confirm( apiVersion: detailViewModel.apiVersion, data: confirmData, - success: { - detailViewModel.state.value = .success - detailViewModel.actionTime = Date() - after(finalAuthorizationTimeToLive) { - self.delegate?.shouldClose() + success: { response in + if response.status.isFinalStatus { + detailViewModel.state.value = .confirmed + detailViewModel.actionTime = Date() + after(finalAuthorizationTimeToLive) { + self.delegate?.shouldClose() + } } }, failure: { _ in - detailViewModel.state.value = .undefined + detailViewModel.state.value = .error detailViewModel.actionTime = Date() after(finalAuthorizationTimeToLive) { self.delegate?.shouldClose() @@ -141,15 +143,17 @@ extension SingleAuthorizationViewModel: AuthorizationDetailEventsDelegate { AuthorizationsInteractor.deny( apiVersion: detailViewModel.apiVersion, data: confirmData, - success: { - detailViewModel.state.value = .denied - detailViewModel.actionTime = Date() - after(finalAuthorizationTimeToLive) { - self.delegate?.shouldClose() + success: { response in + if response.status.isFinalStatus { + detailViewModel.state.value = .denied + detailViewModel.actionTime = Date() + after(finalAuthorizationTimeToLive) { + self.delegate?.shouldClose() + } } }, failure: { _ in - detailViewModel.state.value = .undefined + detailViewModel.state.value = .error detailViewModel.actionTime = Date() after(finalAuthorizationTimeToLive) { self.delegate?.shouldClose() @@ -159,7 +163,7 @@ extension SingleAuthorizationViewModel: AuthorizationDetailEventsDelegate { } func authorizationExpired() { - detailViewModel?.state.value = .expired + detailViewModel?.state.value = .timeOut after(finalAuthorizationTimeToLive) { self.delegate?.shouldClose() } diff --git a/Example/Authenticator/Views/Authorizations/AuthorizationContentView.swift b/Example/Authenticator/Views/Authorizations/AuthorizationContentView.swift index 20819efc..fceb10fd 100644 --- a/Example/Authenticator/Views/Authorizations/AuthorizationContentView.swift +++ b/Example/Authenticator/Views/Authorizations/AuthorizationContentView.swift @@ -68,8 +68,8 @@ final class AuthorizationContentView: UIView { return } - if viewModel.expired && viewModel.state.value != .expired { - stateView.set(state: .expired) + if viewModel.expired && viewModel.state.value != .timeOut { + stateView.set(state: .timeOut) } else { stateView.set(state: .base) diff --git a/Example/Authenticator/Views/Authorizations/AuthorizationStateView.swift b/Example/Authenticator/Views/Authorizations/AuthorizationStateView.swift index ae223538..75ee1ac3 100644 --- a/Example/Authenticator/Views/Authorizations/AuthorizationStateView.swift +++ b/Example/Authenticator/Views/Authorizations/AuthorizationStateView.swift @@ -38,18 +38,20 @@ final class AuthorizationStateView: UIView { enum AuthorizationState: String, Equatable { case base case denied - case expired case processing - case success + case confirmed + case error + case timeOut + case unavailable case undefined var title: String { switch self { case .processing: return l10n(.active) - case .success: return l10n(.successfulAuthorization) - case .expired: return l10n(.timeOut) + case .confirmed: return l10n(.successfulAuthorization) + case .timeOut: return l10n(.timeOut) case .denied: return l10n(.denied) - case .undefined: return l10n(.somethingWentWrong) + case .error, .unavailable: return l10n(.somethingWentWrong) default: return "" } } @@ -57,28 +59,29 @@ final class AuthorizationStateView: UIView { var message: String { switch self { case .processing: return l10n(.activeMessage) - case .success: return l10n(.successfulAuthorizationMessage) - case .expired: return l10n(.timeOutMessage) + case .confirmed: return l10n(.successfulAuthorizationMessage) + case .timeOut: return l10n(.timeOutMessage) case .denied: return l10n(.deniedMessage) - case .undefined: return l10n(.errorOccuredPleaseTryAgain) + case .error, .unavailable: return l10n(.errorOccuredPleaseTryAgain) default: return "" } } var topAccessoryView: UIView { switch self { - case .success: return AspectFitImageView(imageName: "success") - case .expired: return AspectFitImageView(imageName: "timeout") + case .confirmed: return AspectFitImageView(imageName: "success") + case .timeOut: return AspectFitImageView(imageName: "timeout") case .denied: return AspectFitImageView(imageName: "deny") - case .undefined: return AspectFitImageView(imageName: "smth_wrong") + case .error, .unavailable: return AspectFitImageView(imageName: "smth_wrong") default: return LoadingIndicatorView() } } static func == (lhs: AuthorizationState, rhs: AuthorizationState) -> Bool { switch (lhs, rhs) { - case (.base, .base), (.denied, .denied), (.expired, .expired), - (.processing, .processing), (.success, .success), (.undefined, .undefined): return true + case (.base, .base), (.denied, .denied), (.processing, .processing), + (.confirmed, .confirmed), (.timeOut, .timeOut), + (.error, .error), (.unavailable, .unavailable) : return true default: return false } } diff --git a/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift b/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift index 1d5c9633..82258e23 100644 --- a/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift +++ b/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift @@ -53,7 +53,7 @@ class AuthorizationsDataSourceSpec: BaseSpec { firstModel = AuthorizationDetailViewModel(firstDecryptedData, apiVersion: "1") secondModel = AuthorizationDetailViewModel(secondDecryptedData, apiVersion: "1") - _ = dataSource.update(with: [firstDecryptedData, secondDecryptedData]) +// _ = dataSource.update(with: [firstDecryptedData, secondDecryptedData]) } afterEach { @@ -171,34 +171,47 @@ class AuthorizationsDataSourceSpec: BaseSpec { } context("merge") { - context("when one authorization expired and other was added") { + fcontext("when one authorization expired and other was added") { it("should keep the expired one and add the new one") { - expect(dataSource.rows).to(equal(2)) - - let authMessage = ["id": "909", - "connection_id": connection.id, - "title": "Expired Authorization", - "description": "Test expired authorization", - "created_at": Date().iso8601string, - "expires_at": Date().addingTimeInterval(1.0).iso8601string] - let decryptedData = SpecUtils.createAuthResponse(with: authMessage, id: connection.id, guid: connection.guid) +// expect(dataSource.rows).to(equal(2)) + + let authMessage: [String: Any] = [ + "title": "Authorization V2", + "authorization_code": "code", + "description": ["text": "Test valid authorization"], + "created_at": Date().iso8601string, + "expires_at": Date().addingTimeInterval(3.0 * 60.0).iso8601string + ] +// let authMessage = ["id": "909", +// "connection_id": connection.id, +// "title": "Expired Authorization", +// "description": "Test expired authorization", +// "created_at": Date().iso8601string, +// "expires_at": Date().addingTimeInterval(1.0).iso8601string] +// let decryptedData = SpecUtils.createAuthResponse(with: authMessage, id: connection.id, guid: connection.guid) + + let decryptedData = SpecUtils.createAuthResponseV2(with: authMessage, authorizationId: 909, connectionId: Int(connection.id)!, guid: connection.guid) _ = dataSource.update(with: [decryptedData]) sleep(1) expect(dataSource.rows).to(equal(1)) - let newAuthMessage = ["id": "910", - "connection_id": connection.id, - "title": "Expired Authorization", - "description": "Test expired authorization", - "created_at": Date().iso8601string, - "expires_at": Date().addingTimeInterval(3.0 * 60.0).iso8601string] - let newDecryptedData = SpecUtils.createAuthResponse(with: newAuthMessage, id: connection.id, guid: connection.guid) +// let newAuthMessage = ["id": "910", +// "connection_id": connection.id, +// "title": "Expired Authorization", +// "description": "Test expired authorization", +// "created_at": Date().iso8601string, +// "expires_at": Date().addingTimeInterval(3.0 * 60.0).iso8601string] + let finalStatusAuthorization = SpecUtils.createFinalAuthResponseV2(with: authMessage, authorizationId: 909, connectionId: Int(connection.id)!, guid: connection.guid) + + _ = dataSource.update(with: [finalStatusAuthorization]) + + expect(dataSource.rows).to(equal(1)) - _ = dataSource.update(with: [newDecryptedData]) + sleep(6) - expect(dataSource.rows).to(equal(2)) + expect(dataSource.rows).to(equal(0)) } } } diff --git a/Example/Tests/Networking/Responses/ConfirmAuthorizationResponseSpec.swift b/Example/Tests/Networking/Responses/ConfirmAuthorizationResponseSpec.swift index 7e8a7c98..5a33e46c 100644 --- a/Example/Tests/Networking/Responses/ConfirmAuthorizationResponseSpec.swift +++ b/Example/Tests/Networking/Responses/ConfirmAuthorizationResponseSpec.swift @@ -22,7 +22,7 @@ import Quick import Nimble -@testable import SEAuthenticator +@testable import SEAuthenticatorCore class ConfirmAuthorizationResponseSpec: BaseSpec { override func spec() { @@ -33,7 +33,7 @@ class ConfirmAuthorizationResponseSpec: BaseSpec { let response = SEConfirmAuthorizationResponse(fixture) expect(response).toNot(beNil()) - expect(response?.success).to(beTrue()) + expect(response?.status.isFinalStatus).to(beTrue()) expect(response?.id).to(equal("1")) } } diff --git a/Example/Tests/Spec Helpers/SpecUtils.swift b/Example/Tests/Spec Helpers/SpecUtils.swift index 77d74657..e753625f 100644 --- a/Example/Tests/Spec Helpers/SpecUtils.swift +++ b/Example/Tests/Spec Helpers/SpecUtils.swift @@ -81,6 +81,25 @@ struct SpecUtils { return SEEncryptedAuthorizationData(dict)!.decryptedAuthorizationDataV2! } + static func createFinalAuthResponseV2( + with authMessage: [String: Any], + authorizationId: Int, + connectionId: Int, + guid: GUID + ) -> SEAuthorizationDataV2 { + let dict: [String: Any] = [ + "data": "", + "key": "", + "iv": "", + "id": authorizationId, + "connection_id": connectionId, + "status": "denied" + ] + + return SEEncryptedAuthorizationData(dict)!.decryptedAuthorizationDataV2! + } + + public static var publicKeyPem: String { "-----BEGIN PUBLIC KEY-----\n" + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAppVU/nZZVewUCRVLz51X\n" + diff --git a/Example/Tests/ViewModels/AuthorizationsViewModelSpec.swift b/Example/Tests/ViewModels/AuthorizationsViewModelSpec.swift index 9c5191bc..26a2417e 100644 --- a/Example/Tests/ViewModels/AuthorizationsViewModelSpec.swift +++ b/Example/Tests/ViewModels/AuthorizationsViewModelSpec.swift @@ -107,7 +107,7 @@ final class AuthorizationsViewModelSpec: BaseSpec { viewModel.confirmAuthorization(by: "00000", apiVersion: "1") expect(detailViewModel?.state.value).to(equal(AuthorizationStateView.AuthorizationState.processing)) - expect(detailViewModel?.state.value).toEventually(equal(AuthorizationStateView.AuthorizationState.undefined)) + expect(detailViewModel?.state.value).toEventually(equal(AuthorizationStateView.AuthorizationState.error)) } context("when location services are off and conection requires geolocation") { @@ -166,7 +166,7 @@ final class AuthorizationsViewModelSpec: BaseSpec { viewModel.denyAuthorization(by: "00000", apiVersion: "1") expect(detailViewModel?.state.value).to(equal(AuthorizationStateView.AuthorizationState.processing)) - expect(detailViewModel?.state.value).toEventually(equal(AuthorizationStateView.AuthorizationState.undefined)) + expect(detailViewModel?.state.value).toEventually(equal(AuthorizationStateView.AuthorizationState.error)) } context("when location services are off and conection requires geolocation") { diff --git a/SaltedgeAuthenticatorCore/Classes/API/Models/SEConfirmAuthorizationResponse.swift b/SaltedgeAuthenticatorCore/Classes/API/Models/SEConfirmAuthorizationResponse.swift new file mode 100644 index 00000000..851302a1 --- /dev/null +++ b/SaltedgeAuthenticatorCore/Classes/API/Models/SEConfirmAuthorizationResponse.swift @@ -0,0 +1,40 @@ +// +// SEConfirmAuthorizationResponse +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation + +public struct SEConfirmAuthorizationResponse: SerializableResponse { + public let id: String + public let status: String + + public init?(_ value: Any) { + if let dict = value as? [String: Any], + let data = dict[SENetKeys.data] as? [String: Any], + let status = data[SENetKeys.status] as? String, + let id = data[SENetKeys.id] as? String { + self.id = id + self.status = status + } else { + return nil + } + } +} diff --git a/SaltedgeAuthenticatorCore/Classes/Helpers/AuthorizationStatus.swift b/SaltedgeAuthenticatorCore/Classes/Helpers/AuthorizationStatus.swift new file mode 100644 index 00000000..e77394cf --- /dev/null +++ b/SaltedgeAuthenticatorCore/Classes/Helpers/AuthorizationStatus.swift @@ -0,0 +1,60 @@ +// +// AuthorizationStatus +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation + +public enum AuthoriztionStatus: String { + case processing + case confirmed + case denied + case error + case timeOut = "time_out" + case unavailable + case confirmProcessing = "confirm_processing" + case denyProcessing = "deny_processing" + + var isFinal: Bool { + return self == .confirmed + || self == .denied + || self == .error + || self == .timeOut + || self == .unavailable + } + + var isProcessing: Bool { + return self == .confirmProcessing || self == .denyProcessing + } +} + +public extension String { + var isFinalStatus: Bool { + guard let status = AuthoriztionStatus(rawValue: self) else { return false } + + return status.isFinal + } + + var isProcessing: Bool { + guard let status = AuthoriztionStatus(rawValue: self) else { return false } + + return status.isProcessing + } +} diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/AuthorizationResponses.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/AuthorizationResponses.swift index aea18cd4..3e4e6feb 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/AuthorizationResponses.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/AuthorizationResponses.swift @@ -47,20 +47,3 @@ public struct SEEncryptedListResponse: SerializableResponse { } } } - -public struct SEConfirmAuthorizationResponse: SerializableResponse { - public let id: String - public let success: Bool - - public init?(_ value: Any) { - if let dict = value as? [String: Any], - let data = dict[SENetKeys.data] as? [String: Any], - let success = data[SENetKeys.success] as? Bool, - let id = data[SENetKeys.id] as? String { - self.id = id - self.success = success - } else { - return nil - } - } -} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEAuthorizationDataV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEAuthorizationDataV2.swift index 92883a59..e30e7e13 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEAuthorizationDataV2.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEAuthorizationDataV2.swift @@ -24,10 +24,10 @@ import Foundation import SEAuthenticatorCore public class SEAuthorizationDataV2: SEBaseAuthorizationData { - public let title: String - public let description: [String: Any] - public var createdAt: Date - public var expiresAt: Date + public var title: String = "" + public var description: [String: Any] = [:] + public var createdAt: Date = Date() + public var expiresAt: Date = Date() public var authorizationCode: String? public var id: String @@ -54,6 +54,12 @@ public class SEAuthorizationDataV2: SEBaseAuthorizationData { return nil } } + + public init(id: String, connectionId: String, status: String) { + self.id = id + self.connectionId = connectionId + self.status = status + } } extension SEAuthorizationDataV2: Equatable { diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/AuthorizationResponses.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/AuthorizationResponses.swift index f9d4e45e..c232ff9e 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/AuthorizationResponses.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/AuthorizationResponses.swift @@ -47,20 +47,3 @@ public struct SEEncryptedAuthorizationsListResponse: SerializableResponse { } } } - -public struct SEConfirmAuthorizationResponse: SerializableResponse { - public let id: String - public let status: String - - public init?(_ value: Any) { - if let dict = value as? [String: Any], - let data = dict[SENetKeys.data] as? [String: Any], - let status = data[SENetKeys.status] as? String, - let id = data[SENetKeys.id] as? String { - self.id = id - self.status = status - } else { - return nil - } - } -} From 2b48c987f27969e6a3e0c1abdb7faebc55079326 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Thu, 8 Jul 2021 17:07:07 +0300 Subject: [PATCH 043/110] cleared and fixed tests --- .../AuthorizationsDataSource.swift | 16 ++++- .../SEEncryptedDataExtensions.swift | 2 +- .../v1/AuthorizationsInteractor.swift | 22 +++--- .../AuthorizationDetailViewModel.swift | 8 +-- .../AuthorizationsViewModel.swift | 33 +++++---- .../SingleAuthorizationViewModel.swift | 46 ++++++------- .../AuthorizationsDataSourceSpec.swift | 68 +++++++++++++------ .../ConfirmAuthorizationResponseSpec.swift | 4 +- .../Classes/Helpers/AuthorizationStatus.swift | 21 ++---- .../SEConfirmAuthorizationResponse.swift | 7 +- .../Managers/SEAuthorizationManagerV2.swift | 8 +-- .../API/Models/SEAuthorizationDataV2.swift | 6 +- .../SEConfirmAuthorizationResponseV2.swift | 42 ++++++++++++ .../API/SEEncryptedAuthorizationData.swift | 5 +- 14 files changed, 178 insertions(+), 110 deletions(-) rename {SaltedgeAuthenticatorCore/Classes/API/Models => SaltedgeAuthenticatorSDK/Classes/API/Models/Responses}/SEConfirmAuthorizationResponse.swift (89%) create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEConfirmAuthorizationResponseV2.swift diff --git a/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift b/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift index 1f7d0509..dcafabad 100644 --- a/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift +++ b/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift @@ -124,15 +124,27 @@ final class AuthorizationsDataSource { } private extension Array where Element == SEBaseAuthorizationData { + /* + Converts SEBaseAuthorizationData to array of AuthorizationDetailViewModel's + + For API v2 at first it checks if status of received authorization response is final, + than it filters existed view models to find the one with the same authorization id. + + For the found view model sets the final state (if it doesn't have the final status already) and return it. + + For API v1 it only checks if this authorization isn't already expired + and pass the received response to AuthorizationDetailViewModel. + */ func toAuthorizationViewModels( apiVersion: ApiVersion, oldViewModels: [AuthorizationDetailViewModel] ) -> [AuthorizationDetailViewModel] { return filter { $0.apiVersion == apiVersion }.compactMap { response in if let v2Response = response as? SEAuthorizationDataV2, - v2Response.status.isFinalStatus, + v2Response.status.isFinal, let filtered = oldViewModels.filter({ $0.authorizationId == v2Response.id }).first { - guard filtered.authorizationExpiresAt >= Date(), !filtered.status.isFinalStatus else { return nil } + guard filtered.authorizationExpiresAt >= Date(), + let filteredStatus = filtered.status, !filteredStatus.isFinal else { return nil } filtered.setFinal(status: v2Response.status) return filtered diff --git a/Example/Authenticator/Utils/Extensions/SEEncryptedDataExtensions.swift b/Example/Authenticator/Utils/Extensions/SEEncryptedDataExtensions.swift index 755bfb97..78951e30 100644 --- a/Example/Authenticator/Utils/Extensions/SEEncryptedDataExtensions.swift +++ b/Example/Authenticator/Utils/Extensions/SEEncryptedDataExtensions.swift @@ -37,7 +37,7 @@ extension SEBaseEncryptedAuthorizationData { guard let v2Response = self as? SEEncryptedAuthorizationData, let connectionId = connectionId else { return nil } - if v2Response.status.isFinalStatus, !v2Response.status.isProcessing { + if v2Response.status.isFinal, !v2Response.status.isProcessing { return SEAuthorizationDataV2( id: v2Response.id, connectionId: connectionId, diff --git a/Example/Authenticator/Utils/Interactors/v1/AuthorizationsInteractor.swift b/Example/Authenticator/Utils/Interactors/v1/AuthorizationsInteractor.swift index 5bfc19db..946df450 100644 --- a/Example/Authenticator/Utils/Interactors/v1/AuthorizationsInteractor.swift +++ b/Example/Authenticator/Utils/Interactors/v1/AuthorizationsInteractor.swift @@ -29,23 +29,20 @@ struct AuthorizationsInteractor { static func confirm( apiVersion: ApiVersion, data: SEConfirmAuthorizationRequestData, - success: ((SEConfirmAuthorizationResponse) -> ())? = nil, + successV1: (() -> ())? = nil, + successV2: ((SEConfirmAuthorizationResponseV2) -> ())? = nil, failure: ((String) -> ())? = nil ) { if apiVersion == "2" { SEAuthorizationManagerV2.confirmAuthorization( data: data, - onSuccess: { response in - success?(response) - }, + onSuccess: { response in successV2?(response) }, onFailure: { error in failure?(error) } ) } else { SEAuthorizationManager.confirmAuthorization( data: data, - onSuccess: { response in - success?(response) - }, + onSuccess: { _ in successV1?() }, onFailure: { error in failure?(error) } ) } @@ -54,23 +51,20 @@ struct AuthorizationsInteractor { static func deny( apiVersion: ApiVersion, data: SEConfirmAuthorizationRequestData, - success: ((SEConfirmAuthorizationResponse) -> ())? = nil, + successV1: (() -> ())? = nil, + successV2: ((SEConfirmAuthorizationResponseV2) -> ())? = nil, failure: ((String) -> ())? = nil ) { if apiVersion == "2" { SEAuthorizationManagerV2.denyAuthorization( data: data, - onSuccess: { response in - success?(response) - }, + onSuccess: { response in successV2?(response) }, onFailure: { error in failure?(error) } ) } else { SEAuthorizationManager.denyAuthorization( data: data, - onSuccess: { response in - success?(response) - }, + onSuccess: { _ in successV1?() }, onFailure: { error in failure?(error) } ) } diff --git a/Example/Authenticator/View Models/Authorizations/AuthorizationDetailViewModel.swift b/Example/Authenticator/View Models/Authorizations/AuthorizationDetailViewModel.swift index 6be5a54e..91ffd7c7 100644 --- a/Example/Authenticator/View Models/Authorizations/AuthorizationDetailViewModel.swift +++ b/Example/Authenticator/View Models/Authorizations/AuthorizationDetailViewModel.swift @@ -36,7 +36,7 @@ final class AuthorizationDetailViewModel: Equatable { var authorizationId: String var connectionId: String var description: String = "" - var status: String = "" + var status: AuthorizationStatus? var descriptionAttributes: [String: Any] = [:] var authorizationCode: String? var lifetime: Int = 0 @@ -101,9 +101,9 @@ final class AuthorizationDetailViewModel: Equatable { delegate?.denyPressed(authorizationId, apiVersion: apiVersion) } - func setFinal(status: String) { - guard status.isFinalStatus, - let authStatus = AuthorizationStateView.AuthorizationState(rawValue: status) else { return } + func setFinal(status: AuthorizationStatus) { + guard status.isFinal, + let authStatus = AuthorizationStateView.AuthorizationState(rawValue: status.rawValue) else { return } self.status = status self.state.value = authStatus diff --git a/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift b/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift index 04083043..48f76cd9 100644 --- a/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift +++ b/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift @@ -116,15 +116,16 @@ class AuthorizationsViewModel { AuthorizationsInteractor.confirm( apiVersion: detailViewModel.apiVersion, data: data, - success: { response in - if response.status.isFinalStatus { - detailViewModel.state.value = .confirmed - detailViewModel.actionTime = Date() + successV1: { + self.update(viewModel: detailViewModel, state: .confirmed) + }, + successV2: { response in + if response.status.isFinal { + self.update(viewModel: detailViewModel, state: .confirmed) } }, failure: { _ in - detailViewModel.state.value = .error - detailViewModel.actionTime = Date() + self.update(viewModel: detailViewModel, state: .error) } ) } @@ -142,20 +143,26 @@ class AuthorizationsViewModel { AuthorizationsInteractor.deny( apiVersion: detailViewModel.apiVersion, data: data, - success: { response in - if response.status.isFinalStatus { - detailViewModel.state.value = .denied - detailViewModel.actionTime = Date() + successV1: { + self.update(viewModel: detailViewModel, state: .denied) + }, + successV2: { response in + if response.status.isFinal { + self.update(viewModel: detailViewModel, state: .denied) } }, failure: { _ in - detailViewModel.state.value = .error - detailViewModel.actionTime = Date() + self.update(viewModel: detailViewModel, state: .error) } ) } } + private func update(viewModel: AuthorizationDetailViewModel, state: AuthorizationStateView.AuthorizationState) { + viewModel.state.value = state + viewModel.actionTime = Date() + } + private func updateDataSource(with authorizations: [SEBaseAuthorizationData]) { if dataSource.update(with: authorizations) { state.value = .reloadData @@ -278,6 +285,8 @@ private extension AuthorizationsViewModel { let decryptedAuthorizationsV1 = encryptedAuthorizations.compactMap { $0.decryptedAuthorizationData } let decryptedAuthorizationsV2 = encryptedAuthorizations.compactMap { $0.decryptedAuthorizationDataV2 } + print("Decrypted Authorizations V2: ", decryptedAuthorizationsV2) + DispatchQueue.main.async { strongSelf.updateDataSource(with: decryptedAuthorizationsV1 + decryptedAuthorizationsV2) } diff --git a/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift b/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift index 77ad1b7e..9945b685 100644 --- a/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift +++ b/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift @@ -105,21 +105,16 @@ extension SingleAuthorizationViewModel: AuthorizationDetailEventsDelegate { AuthorizationsInteractor.confirm( apiVersion: detailViewModel.apiVersion, data: confirmData, - success: { response in - if response.status.isFinalStatus { - detailViewModel.state.value = .confirmed - detailViewModel.actionTime = Date() - after(finalAuthorizationTimeToLive) { - self.delegate?.shouldClose() - } + successV1: { + self.updateDetailViewModel(state: .confirmed) + }, + successV2: { response in + if response.status.isFinal { + self.updateDetailViewModel(state: .confirmed) } }, failure: { _ in - detailViewModel.state.value = .error - detailViewModel.actionTime = Date() - after(finalAuthorizationTimeToLive) { - self.delegate?.shouldClose() - } + self.updateDetailViewModel(state: .error) } ) } @@ -143,21 +138,16 @@ extension SingleAuthorizationViewModel: AuthorizationDetailEventsDelegate { AuthorizationsInteractor.deny( apiVersion: detailViewModel.apiVersion, data: confirmData, - success: { response in - if response.status.isFinalStatus { - detailViewModel.state.value = .denied - detailViewModel.actionTime = Date() - after(finalAuthorizationTimeToLive) { - self.delegate?.shouldClose() - } + successV1: { + self.updateDetailViewModel(state: .denied) + }, + successV2: { response in + if response.status.isFinal { + self.updateDetailViewModel(state: .denied) } }, failure: { _ in - detailViewModel.state.value = .error - detailViewModel.actionTime = Date() - after(finalAuthorizationTimeToLive) { - self.delegate?.shouldClose() - } + self.updateDetailViewModel(state: .error) } ) } @@ -168,4 +158,12 @@ extension SingleAuthorizationViewModel: AuthorizationDetailEventsDelegate { self.delegate?.shouldClose() } } + + private func updateDetailViewModel(state: AuthorizationStateView.AuthorizationState) { + detailViewModel?.state.value = state + detailViewModel?.actionTime = Date() + after(finalAuthorizationTimeToLive) { + self.delegate?.shouldClose() + } + } } diff --git a/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift b/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift index 82258e23..1985aa64 100644 --- a/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift +++ b/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift @@ -53,7 +53,7 @@ class AuthorizationsDataSourceSpec: BaseSpec { firstModel = AuthorizationDetailViewModel(firstDecryptedData, apiVersion: "1") secondModel = AuthorizationDetailViewModel(secondDecryptedData, apiVersion: "1") -// _ = dataSource.update(with: [firstDecryptedData, secondDecryptedData]) + _ = dataSource.update(with: [firstDecryptedData, secondDecryptedData]) } afterEach { @@ -171,9 +171,40 @@ class AuthorizationsDataSourceSpec: BaseSpec { } context("merge") { - fcontext("when one authorization expired and other was added") { + context("when one authorization expired and other was added") { it("should keep the expired one and add the new one") { -// expect(dataSource.rows).to(equal(2)) + expect(dataSource.rows).to(equal(2)) + + let authMessage = ["id": "909", + "connection_id": connection.id, + "title": "Expired Authorization", + "description": "Test expired authorization", + "created_at": Date().iso8601string, + "expires_at": Date().addingTimeInterval(1.0).iso8601string] + let decryptedData = SpecUtils.createAuthResponse(with: authMessage, id: connection.id, guid: connection.guid) + + _ = dataSource.update(with: [decryptedData]) + sleep(1) + + expect(dataSource.rows).to(equal(1)) + + let newAuthMessage = ["id": "910", + "connection_id": connection.id, + "title": "Expired Authorization", + "description": "Test expired authorization", + "created_at": Date().iso8601string, + "expires_at": Date().addingTimeInterval(3.0 * 60.0).iso8601string] + let newDecryptedData = SpecUtils.createAuthResponse(with: newAuthMessage, id: connection.id, guid: connection.guid) + + _ = dataSource.update(with: [newDecryptedData]) + + expect(dataSource.rows).to(equal(2)) + } + } + + context("when recieved authorization with final state from v2") { + it("should set final state and leave only one") { + expect(dataSource.rows).to(equal(2)) let authMessage: [String: Any] = [ "title": "Authorization V2", @@ -182,36 +213,29 @@ class AuthorizationsDataSourceSpec: BaseSpec { "created_at": Date().iso8601string, "expires_at": Date().addingTimeInterval(3.0 * 60.0).iso8601string ] -// let authMessage = ["id": "909", -// "connection_id": connection.id, -// "title": "Expired Authorization", -// "description": "Test expired authorization", -// "created_at": Date().iso8601string, -// "expires_at": Date().addingTimeInterval(1.0).iso8601string] -// let decryptedData = SpecUtils.createAuthResponse(with: authMessage, id: connection.id, guid: connection.guid) - let decryptedData = SpecUtils.createAuthResponseV2(with: authMessage, authorizationId: 909, connectionId: Int(connection.id)!, guid: connection.guid) + let decryptedData = SpecUtils.createAuthResponseV2( + with: authMessage, + authorizationId: 909, + connectionId: Int(connection.id)!, + guid: connection.guid + ) _ = dataSource.update(with: [decryptedData]) sleep(1) expect(dataSource.rows).to(equal(1)) -// let newAuthMessage = ["id": "910", -// "connection_id": connection.id, -// "title": "Expired Authorization", -// "description": "Test expired authorization", -// "created_at": Date().iso8601string, -// "expires_at": Date().addingTimeInterval(3.0 * 60.0).iso8601string] - let finalStatusAuthorization = SpecUtils.createFinalAuthResponseV2(with: authMessage, authorizationId: 909, connectionId: Int(connection.id)!, guid: connection.guid) + let finalStatusAuthorization = SpecUtils.createFinalAuthResponseV2( + with: authMessage, + authorizationId: 909, + connectionId: Int(connection.id)!, + guid: connection.guid + ) _ = dataSource.update(with: [finalStatusAuthorization]) expect(dataSource.rows).to(equal(1)) - - sleep(6) - - expect(dataSource.rows).to(equal(0)) } } } diff --git a/Example/Tests/Networking/Responses/ConfirmAuthorizationResponseSpec.swift b/Example/Tests/Networking/Responses/ConfirmAuthorizationResponseSpec.swift index 5a33e46c..7e8a7c98 100644 --- a/Example/Tests/Networking/Responses/ConfirmAuthorizationResponseSpec.swift +++ b/Example/Tests/Networking/Responses/ConfirmAuthorizationResponseSpec.swift @@ -22,7 +22,7 @@ import Quick import Nimble -@testable import SEAuthenticatorCore +@testable import SEAuthenticator class ConfirmAuthorizationResponseSpec: BaseSpec { override func spec() { @@ -33,7 +33,7 @@ class ConfirmAuthorizationResponseSpec: BaseSpec { let response = SEConfirmAuthorizationResponse(fixture) expect(response).toNot(beNil()) - expect(response?.status.isFinalStatus).to(beTrue()) + expect(response?.success).to(beTrue()) expect(response?.id).to(equal("1")) } } diff --git a/SaltedgeAuthenticatorCore/Classes/Helpers/AuthorizationStatus.swift b/SaltedgeAuthenticatorCore/Classes/Helpers/AuthorizationStatus.swift index e77394cf..53f44794 100644 --- a/SaltedgeAuthenticatorCore/Classes/Helpers/AuthorizationStatus.swift +++ b/SaltedgeAuthenticatorCore/Classes/Helpers/AuthorizationStatus.swift @@ -22,7 +22,8 @@ import Foundation -public enum AuthoriztionStatus: String { +public enum AuthorizationStatus: String { + case pending case processing case confirmed case denied @@ -32,7 +33,7 @@ public enum AuthoriztionStatus: String { case confirmProcessing = "confirm_processing" case denyProcessing = "deny_processing" - var isFinal: Bool { + public var isFinal: Bool { return self == .confirmed || self == .denied || self == .error @@ -40,21 +41,7 @@ public enum AuthoriztionStatus: String { || self == .unavailable } - var isProcessing: Bool { + public var isProcessing: Bool { return self == .confirmProcessing || self == .denyProcessing } } - -public extension String { - var isFinalStatus: Bool { - guard let status = AuthoriztionStatus(rawValue: self) else { return false } - - return status.isFinal - } - - var isProcessing: Bool { - guard let status = AuthoriztionStatus(rawValue: self) else { return false } - - return status.isProcessing - } -} diff --git a/SaltedgeAuthenticatorCore/Classes/API/Models/SEConfirmAuthorizationResponse.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SEConfirmAuthorizationResponse.swift similarity index 89% rename from SaltedgeAuthenticatorCore/Classes/API/Models/SEConfirmAuthorizationResponse.swift rename to SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SEConfirmAuthorizationResponse.swift index 851302a1..4b6596a9 100644 --- a/SaltedgeAuthenticatorCore/Classes/API/Models/SEConfirmAuthorizationResponse.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SEConfirmAuthorizationResponse.swift @@ -21,18 +21,19 @@ // import Foundation +import SEAuthenticatorCore public struct SEConfirmAuthorizationResponse: SerializableResponse { public let id: String - public let status: String + public let success: Bool public init?(_ value: Any) { if let dict = value as? [String: Any], let data = dict[SENetKeys.data] as? [String: Any], - let status = data[SENetKeys.status] as? String, + let success = data[SENetKeys.success] as? Bool, let id = data[SENetKeys.id] as? String { self.id = id - self.status = status + self.success = success } else { return nil } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEAuthorizationManagerV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEAuthorizationManagerV2.swift index 75a1edd6..ca1116da 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEAuthorizationManagerV2.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEAuthorizationManagerV2.swift @@ -50,7 +50,7 @@ public struct SEAuthorizationManagerV2 { public static func confirmAuthorization( data: SEConfirmAuthorizationRequestData, - onSuccess success: @escaping HTTPServiceSuccessClosure, + onSuccess success: @escaping HTTPServiceSuccessClosure, onFailure failure: @escaping FailureBlock ) { let parameters = RequestParametersBuilder.confirmAuthorizationParams( @@ -58,7 +58,7 @@ public struct SEAuthorizationManagerV2 { exp: Date().addingTimeInterval(5.0 * 60.0).utcSeconds ) - HTTPService.execute( + HTTPService.execute( request: SEAuthorizationRouter.confirm(data, parameters), success: success, failure: failure @@ -67,7 +67,7 @@ public struct SEAuthorizationManagerV2 { public static func denyAuthorization( data: SEConfirmAuthorizationRequestData, - onSuccess success: @escaping HTTPServiceSuccessClosure, + onSuccess success: @escaping HTTPServiceSuccessClosure, onFailure failure: @escaping FailureBlock ) { let parameters = RequestParametersBuilder.confirmAuthorizationParams( @@ -75,7 +75,7 @@ public struct SEAuthorizationManagerV2 { exp: Date().addingTimeInterval(5.0 * 60.0).utcSeconds ) - HTTPService.execute( + HTTPService.execute( request: SEAuthorizationRouter.deny(data, parameters), success: success, failure: failure diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEAuthorizationDataV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEAuthorizationDataV2.swift index e30e7e13..2f917bd9 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEAuthorizationDataV2.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEAuthorizationDataV2.swift @@ -32,11 +32,11 @@ public class SEAuthorizationDataV2: SEBaseAuthorizationData { public var id: String public var connectionId: String - public var status: String + public var status: AuthorizationStatus public var apiVersion: ApiVersion = "2" - public init?(_ dictionary: [String: Any], id: String, connectionId: String, status: String) { + public init?(_ dictionary: [String: Any], id: String, connectionId: String, status: AuthorizationStatus) { if let title = dictionary[SENetKeys.title] as? String, let description = dictionary[SENetKeys.description] as? [String: Any], let createdAt = (dictionary[SENetKeys.createdAt] as? String)?.iso8601date, @@ -55,7 +55,7 @@ public class SEAuthorizationDataV2: SEBaseAuthorizationData { } } - public init(id: String, connectionId: String, status: String) { + public init(id: String, connectionId: String, status: AuthorizationStatus) { self.id = id self.connectionId = connectionId self.status = status diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEConfirmAuthorizationResponseV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEConfirmAuthorizationResponseV2.swift new file mode 100644 index 00000000..a79506c4 --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEConfirmAuthorizationResponseV2.swift @@ -0,0 +1,42 @@ +// +// SEConfirmAuthorizationResponseV2 +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +public struct SEConfirmAuthorizationResponseV2: SerializableResponse { + public let id: String + public let status: AuthorizationStatus + + public init?(_ value: Any) { + if let dict = value as? [String: Any], + let data = dict[SENetKeys.data] as? [String: Any], + let statusString = data[SENetKeys.status] as? String, + let status = AuthorizationStatus(rawValue: statusString), + let id = data[SENetKeys.id] as? String { + self.id = id + self.status = status + } else { + return nil + } + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/SEEncryptedAuthorizationData.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/SEEncryptedAuthorizationData.swift index 0cf9344c..11ef7b4f 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/SEEncryptedAuthorizationData.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/SEEncryptedAuthorizationData.swift @@ -28,7 +28,7 @@ public struct SEEncryptedAuthorizationData: SEBaseEncryptedAuthorizationData, Se public let data: String public let key: String public let iv: String - public let status: String + public let status: AuthorizationStatus public var connectionId: String? public init?(_ value: Any) { @@ -37,7 +37,8 @@ public struct SEEncryptedAuthorizationData: SEBaseEncryptedAuthorizationData, Se let data = dict[SENetKeys.data] as? String, let key = dict[SENetKeys.key] as? String, let iv = dict[SENetKeys.iv] as? String, - let status = dict[SENetKeys.status] as? String, + let statusString = dict[SENetKeys.status] as? String, + let status = AuthorizationStatus(rawValue: statusString), let connectionId = dict[SENetKeys.connectionId] as? Int { self.id = "\(id)" self.data = data From 484050e660f0a8588da99b07e08b09da1ed1adf2 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Thu, 8 Jul 2021 17:11:35 +0300 Subject: [PATCH 044/110] cleared --- .../Data Sources/AuthorizationsDataSource.swift | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift b/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift index dcafabad..670f89d0 100644 --- a/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift +++ b/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift @@ -29,10 +29,10 @@ final class AuthorizationsDataSource { private var viewModels = [AuthorizationDetailViewModel]() func update(with baseData: [SEBaseAuthorizationData]) -> Bool { - let viewModelsV1 = baseData.toAuthorizationViewModels(apiVersion: "1", oldViewModels: viewModels) + let viewModelsV1 = baseData.toAuthorizationViewModels(apiVersion: "1", existedViewModels: viewModels) .merge(array: self.viewModels.filter { $0.apiVersion == "1" }) - let viewModelsV2 = baseData.toAuthorizationViewModels(apiVersion: "2", oldViewModels: viewModels) + let viewModelsV2 = baseData.toAuthorizationViewModels(apiVersion: "2", existedViewModels: viewModels) .merge(array: self.viewModels.filter { $0.apiVersion == "2" }) let allViewModels = (viewModelsV1 + viewModelsV2).sorted(by: { $0.createdAt < $1.createdAt }) @@ -126,6 +126,10 @@ final class AuthorizationsDataSource { private extension Array where Element == SEBaseAuthorizationData { /* Converts SEBaseAuthorizationData to array of AuthorizationDetailViewModel's + + - parameters: + - apiVersion: The API version of received authorization responses + - existedViewModels: The array of already existed authorization view models For API v2 at first it checks if status of received authorization response is final, than it filters existed view models to find the one with the same authorization id. @@ -137,12 +141,12 @@ private extension Array where Element == SEBaseAuthorizationData { */ func toAuthorizationViewModels( apiVersion: ApiVersion, - oldViewModels: [AuthorizationDetailViewModel] + existedViewModels: [AuthorizationDetailViewModel] ) -> [AuthorizationDetailViewModel] { return filter { $0.apiVersion == apiVersion }.compactMap { response in if let v2Response = response as? SEAuthorizationDataV2, v2Response.status.isFinal, - let filtered = oldViewModels.filter({ $0.authorizationId == v2Response.id }).first { + let filtered = existedViewModels.filter({ $0.authorizationId == v2Response.id }).first { guard filtered.authorizationExpiresAt >= Date(), let filteredStatus = filtered.status, !filteredStatus.isFinal else { return nil } From 16d32ee4394bbc4b7e657bb97a5ce02496d7234f Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Thu, 8 Jul 2021 17:39:55 +0300 Subject: [PATCH 045/110] added spec --- .../SEEncryptedDataExtensions.swift | 2 +- .../AuthorizationsViewModel.swift | 2 - .../SEEncryptedDataExtensionsSpec.swift | 75 ++++++++++++++----- .../Classes/DateExtensions.swift | 7 ++ .../API/Models/SEAuthorizationDataV2.swift | 7 +- 5 files changed, 67 insertions(+), 26 deletions(-) diff --git a/Example/Authenticator/Utils/Extensions/SEEncryptedDataExtensions.swift b/Example/Authenticator/Utils/Extensions/SEEncryptedDataExtensions.swift index 78951e30..59160f75 100644 --- a/Example/Authenticator/Utils/Extensions/SEEncryptedDataExtensions.swift +++ b/Example/Authenticator/Utils/Extensions/SEEncryptedDataExtensions.swift @@ -37,7 +37,7 @@ extension SEBaseEncryptedAuthorizationData { guard let v2Response = self as? SEEncryptedAuthorizationData, let connectionId = connectionId else { return nil } - if v2Response.status.isFinal, !v2Response.status.isProcessing { + if v2Response.status.isFinal { return SEAuthorizationDataV2( id: v2Response.id, connectionId: connectionId, diff --git a/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift b/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift index 48f76cd9..b179b4f9 100644 --- a/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift +++ b/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift @@ -285,8 +285,6 @@ private extension AuthorizationsViewModel { let decryptedAuthorizationsV1 = encryptedAuthorizations.compactMap { $0.decryptedAuthorizationData } let decryptedAuthorizationsV2 = encryptedAuthorizations.compactMap { $0.decryptedAuthorizationDataV2 } - print("Decrypted Authorizations V2: ", decryptedAuthorizationsV2) - DispatchQueue.main.async { strongSelf.updateDataSource(with: decryptedAuthorizationsV1 + decryptedAuthorizationsV2) } diff --git a/Example/Tests/Extensions/SEEncryptedDataExtensionsSpec.swift b/Example/Tests/Extensions/SEEncryptedDataExtensionsSpec.swift index df80335a..a8c0b070 100644 --- a/Example/Tests/Extensions/SEEncryptedDataExtensionsSpec.swift +++ b/Example/Tests/Extensions/SEEncryptedDataExtensionsSpec.swift @@ -82,28 +82,63 @@ class SEEncryptedDataExtensionsSpec: BaseSpec { } describe("decryptedAuthorizationDataV2") { - it("should return decrypted data from given response") { - let connection = SpecUtils.createConnection(id: "2", apiVersion: "2") - let authorizationId = "00000" + context("when authorization status is not final") { + it("should return decrypted data from given response") { + let connection = SpecUtils.createConnection(id: "2", apiVersion: "2") + let authorizationId = "150" + + let authMessage: [String: Any] = [ + "title": "Authorization V2", + "authorization_code": "code", + "description": ["text": "Test valid authorization"], + "created_at": Date().iso8601string, + "expires_at": Date().addingTimeInterval(3.0 * 60.0).iso8601string + ] + + let encryptedData = try! SECryptoHelper.encrypt(authMessage.jsonString!, tag: SETagHelper.create(for: connection.guid)) - let authMessage = ["id": authorizationId, - "connection_id": connection.id, - "title": "Authorization", - "description": "Test authorization", - "created_at": Date().iso8601string, - "expires_at": Date().addingTimeInterval(5.0 * 60.0).iso8601string] - let encryptedData = try! SECryptoHelper.encrypt(authMessage.jsonString!, tag: SETagHelper.create(for: connection.guid)) - let dict: [String: Any] = [ - "data": encryptedData.data, - "key": encryptedData.key, - "iv": encryptedData.iv, - "id": Int(authorizationId)!, - "connection_id": Int(connection.id)!, - "status": "pending" - ] - let expectedData = SEAuthorizationData(authMessage) + let dict: [String: Any] = [ + "data": encryptedData.data, + "key": encryptedData.key, + "iv": encryptedData.iv, + "id": Int(authorizationId)!, + "connection_id": Int(connection.id)!, + "status": "pending" + ] - expect(expectedData).to(equal(SEEncryptedAuthorizationData(dict)!.decryptedAuthorizationData!)) + let expectedData = SEAuthorizationDataV2( + authMessage, + id: authorizationId, + connectionId: connection.id, + status: .pending + ) + + expect(expectedData).to(equal(SEEncryptedAuthorizationData(dict)!.decryptedAuthorizationDataV2!)) + } + } + + context("when authorization status is final") { + it("should return SEAuthorizationDataV2 from given response") { + let connection = SpecUtils.createConnection(id: "2", apiVersion: "2") + let authorizationId = "150" + + let dict: [String: Any] = [ + "data": "", + "key": "", + "iv": "", + "id": Int(authorizationId)!, + "connection_id": Int(connection.id)!, + "status": "confirmed" + ] + + let expectedData = SEAuthorizationDataV2( + id: authorizationId, + connectionId: connection.id, + status: .confirmed + ) + + expect(expectedData).to(equal(SEEncryptedAuthorizationData(dict)!.decryptedAuthorizationDataV2!)) + } } } } diff --git a/SaltedgeAuthenticatorCore/Classes/DateExtensions.swift b/SaltedgeAuthenticatorCore/Classes/DateExtensions.swift index 451515cd..19ae7ec7 100644 --- a/SaltedgeAuthenticatorCore/Classes/DateExtensions.swift +++ b/SaltedgeAuthenticatorCore/Classes/DateExtensions.swift @@ -33,6 +33,13 @@ public extension Date { return Int(dateFromComponents.timeIntervalSince1970) } + var withoutTime: Date { + var currentCalendar = Calendar.current + currentCalendar.timeZone = TimeZone.utc + let components = Calendar.current.dateComponents([.day, .month, .year], from: self) + return currentCalendar.date(from: components)! + } + func get(_ component: Calendar.Component, calendar: Calendar = Calendar.current) -> Int { return calendar.component(component, from: self) } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEAuthorizationDataV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEAuthorizationDataV2.swift index 2f917bd9..db4d4740 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEAuthorizationDataV2.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEAuthorizationDataV2.swift @@ -66,9 +66,10 @@ extension SEAuthorizationDataV2: Equatable { public static func == (lhs: SEAuthorizationDataV2, rhs: SEAuthorizationDataV2) -> Bool { return lhs.title == rhs.title && lhs.description == rhs.description && - lhs.createdAt == rhs.createdAt && - lhs.expiresAt == rhs.expiresAt && - lhs.authorizationCode == rhs.authorizationCode + lhs.createdAt.withoutTime == rhs.createdAt.withoutTime && + lhs.expiresAt.withoutTime == rhs.expiresAt.withoutTime && + lhs.authorizationCode == rhs.authorizationCode && + lhs.status.rawValue == rhs.status.rawValue } } From cdaf614d1e871a615b416d6c4b3224b0ca58b0a0 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Fri, 9 Jul 2021 12:03:52 +0300 Subject: [PATCH 046/110] cleared --- .../Authenticator.xcodeproj/project.pbxproj | 4 ++-- .../AuthorizationsDataSource.swift | 20 ++++++++++--------- .../Utils/Helpers/Constants.swift | 2 ++ .../AuthorizationsInteractor.swift | 0 .../{v1 => Base}/CollectionsInteractor.swift | 0 5 files changed, 15 insertions(+), 11 deletions(-) rename Example/Authenticator/Utils/Interactors/{v1 => Base}/AuthorizationsInteractor.swift (100%) rename Example/Authenticator/Utils/Interactors/{v1 => Base}/CollectionsInteractor.swift (100%) diff --git a/Example/Authenticator.xcodeproj/project.pbxproj b/Example/Authenticator.xcodeproj/project.pbxproj index 60f9a320..bcddeb46 100644 --- a/Example/Authenticator.xcodeproj/project.pbxproj +++ b/Example/Authenticator.xcodeproj/project.pbxproj @@ -992,10 +992,8 @@ 9DCC5E23265D1DBD0054B933 /* v1 */ = { isa = PBXGroup; children = ( - 9DCED82C22CE18270050ED3C /* AuthorizationsInteractor.swift */, 9DCED82D22CE18270050ED3C /* ConnectionsInteractor.swift */, 9D98747B24B390F200EE89BB /* ConsentsInteractor.swift */, - 95C6781E24A9FD8B0088A9CF /* CollectionsInteractor.swift */, ); path = v1; sourceTree = ""; @@ -1353,6 +1351,8 @@ isa = PBXGroup; children = ( 9DD155F226613CEA00AEFA0D /* BaseConnectionsInteractor.swift */, + 9DCED82C22CE18270050ED3C /* AuthorizationsInteractor.swift */, + 95C6781E24A9FD8B0088A9CF /* CollectionsInteractor.swift */, ); path = Base; sourceTree = ""; diff --git a/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift b/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift index 670f89d0..1e24824b 100644 --- a/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift +++ b/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift @@ -29,11 +29,15 @@ final class AuthorizationsDataSource { private var viewModels = [AuthorizationDetailViewModel]() func update(with baseData: [SEBaseAuthorizationData]) -> Bool { - let viewModelsV1 = baseData.toAuthorizationViewModels(apiVersion: "1", existedViewModels: viewModels) - .merge(array: self.viewModels.filter { $0.apiVersion == "1" }) + let viewModelsV1 = baseData + .filter { $0.apiVersion == API_V1_VERSION } + .toAuthorizationViewModels(existedViewModels: viewModels) + .merge(array: self.viewModels.filter { $0.apiVersion == API_V1_VERSION }) - let viewModelsV2 = baseData.toAuthorizationViewModels(apiVersion: "2", existedViewModels: viewModels) - .merge(array: self.viewModels.filter { $0.apiVersion == "2" }) + let viewModelsV2 = baseData + .filter { $0.apiVersion == API_V2_VERSION } + .toAuthorizationViewModels(existedViewModels: viewModels) + .merge(array: self.viewModels.filter { $0.apiVersion == API_V2_VERSION }) let allViewModels = (viewModelsV1 + viewModelsV2).sorted(by: { $0.createdAt < $1.createdAt }) @@ -126,24 +130,22 @@ final class AuthorizationsDataSource { private extension Array where Element == SEBaseAuthorizationData { /* Converts SEBaseAuthorizationData to array of AuthorizationDetailViewModel's - + - parameters: - - apiVersion: The API version of received authorization responses - existedViewModels: The array of already existed authorization view models For API v2 at first it checks if status of received authorization response is final, than it filters existed view models to find the one with the same authorization id. For the found view model sets the final state (if it doesn't have the final status already) and return it. - + For API v1 it only checks if this authorization isn't already expired and pass the received response to AuthorizationDetailViewModel. */ func toAuthorizationViewModels( - apiVersion: ApiVersion, existedViewModels: [AuthorizationDetailViewModel] ) -> [AuthorizationDetailViewModel] { - return filter { $0.apiVersion == apiVersion }.compactMap { response in + return compactMap { response in if let v2Response = response as? SEAuthorizationDataV2, v2Response.status.isFinal, let filtered = existedViewModels.filter({ $0.authorizationId == v2Response.id }).first { diff --git a/Example/Authenticator/Utils/Helpers/Constants.swift b/Example/Authenticator/Utils/Helpers/Constants.swift index c8a9c1b6..53a44339 100644 --- a/Example/Authenticator/Utils/Helpers/Constants.swift +++ b/Example/Authenticator/Utils/Helpers/Constants.swift @@ -32,6 +32,8 @@ func after(_ time: Double, _ doBlock: @escaping () -> ()) { } let finalAuthorizationTimeToLive: Double = 4.0 +let API_V1_VERSION = "1" +let API_V2_VERSION = "2" struct AnimationConstants { static let defaultDuration: CGFloat = 0.4 diff --git a/Example/Authenticator/Utils/Interactors/v1/AuthorizationsInteractor.swift b/Example/Authenticator/Utils/Interactors/Base/AuthorizationsInteractor.swift similarity index 100% rename from Example/Authenticator/Utils/Interactors/v1/AuthorizationsInteractor.swift rename to Example/Authenticator/Utils/Interactors/Base/AuthorizationsInteractor.swift diff --git a/Example/Authenticator/Utils/Interactors/v1/CollectionsInteractor.swift b/Example/Authenticator/Utils/Interactors/Base/CollectionsInteractor.swift similarity index 100% rename from Example/Authenticator/Utils/Interactors/v1/CollectionsInteractor.swift rename to Example/Authenticator/Utils/Interactors/Base/CollectionsInteractor.swift From 3075371db761257d9c3d46fca496db64d603961b Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Wed, 14 Jul 2021 16:02:54 +0300 Subject: [PATCH 047/110] added consents v2 logic --- .../Connections/ConnectionsCoordinator.swift | 2 +- .../ConsentDetailCoordinator.swift | 2 +- .../Base/CollectionsInteractor.swift | 20 +++-- .../Interactors/v1/ConsentsInteractor.swift | 23 +++--- .../Connections/ConsentDetailViewModel.swift | 2 +- .../Consents/ConsentSharedDataView.swift | 2 +- .../Classes/API/Models/SEConsentData.swift | 14 ++-- .../Classes/API/SEEncryptedData.swift | 25 ++++++ ...tsManager.swift => SEConsentManager.swift} | 8 +- .../Responses/AuthorizationResponses.swift | 48 ++++++------ ...entsRouter.swift => SEConsentRouter.swift} | 4 +- .../API/Managers/SEConsentManagerV2.swift | 50 ++++++++++++ .../Responses/SERevokeConsentResponseV2.swift | 38 +++++++++ .../API/Routers/SEConsentRouterV2.swift | 78 +++++++++++++++++++ .../Classes/RequestParametersBuilder.swift | 7 ++ 15 files changed, 268 insertions(+), 55 deletions(-) rename {SaltedgeAuthenticatorSDK => SaltedgeAuthenticatorCore}/Classes/API/Models/SEConsentData.swift (95%) rename SaltedgeAuthenticatorSDK/Classes/API/Managers/{SEConsentsManager.swift => SEConsentManager.swift} (91%) rename SaltedgeAuthenticatorSDK/Classes/API/Routers/{SEConsentsRouter.swift => SEConsentRouter.swift} (98%) create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConsentManagerV2.swift create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConsentResponseV2.swift create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConsentRouterV2.swift diff --git a/Example/Authenticator/Coordinators/Connections/ConnectionsCoordinator.swift b/Example/Authenticator/Coordinators/Connections/ConnectionsCoordinator.swift index 2e601d44..08c85a6c 100644 --- a/Example/Authenticator/Coordinators/Connections/ConnectionsCoordinator.swift +++ b/Example/Authenticator/Coordinators/Connections/ConnectionsCoordinator.swift @@ -21,7 +21,7 @@ // import UIKit -import SEAuthenticator +import SEAuthenticatorCore final class ConnectionsCoordinator: Coordinator { private var rootViewController: UIViewController diff --git a/Example/Authenticator/Coordinators/Connections/ConsentDetailCoordinator.swift b/Example/Authenticator/Coordinators/Connections/ConsentDetailCoordinator.swift index 4594363e..11b013a1 100644 --- a/Example/Authenticator/Coordinators/Connections/ConsentDetailCoordinator.swift +++ b/Example/Authenticator/Coordinators/Connections/ConsentDetailCoordinator.swift @@ -21,7 +21,7 @@ // import UIKit -import SEAuthenticator +import SEAuthenticatorCore final class ConsentDetailCoordinator: Coordinator { private var rootViewController: UIViewController diff --git a/Example/Authenticator/Utils/Interactors/Base/CollectionsInteractor.swift b/Example/Authenticator/Utils/Interactors/Base/CollectionsInteractor.swift index d1c60f4a..f1a5466f 100644 --- a/Example/Authenticator/Utils/Interactors/Base/CollectionsInteractor.swift +++ b/Example/Authenticator/Utils/Interactors/Base/CollectionsInteractor.swift @@ -69,11 +69,19 @@ enum CollectionsInteractor { ) } case .consents: - SEConsentsManager.getEncryptedConsents( - data: requestData, - onSuccess: { response in success(response.data) }, - onFailure: { error in onFailure(error: error, connection: connection) } - ) + if connection.isApiV2 { + SEConsentManagerV2.getEncryptedConsents( + data: requestData, + onSuccess: { response in success(response.data) }, + onFailure: { error in onFailure(error: error, connection: connection) } + ) + } else { + SEConsentManager.getEncryptedConsents( + data: requestData, + onSuccess: { response in success(response.data) }, + onFailure: { error in onFailure(error: error, connection: connection) } + ) + } } } @@ -137,7 +145,7 @@ enum CollectionsInteractor { ) } case .consents: - SEConsentsManager.getEncryptedConsents( + SEConsentManager.getEncryptedConsents( data: requestData, onSuccess: { response in onSuccess(data: response.data) }, onFailure: { error in onFailure(error: error, connection: connection) } diff --git a/Example/Authenticator/Utils/Interactors/v1/ConsentsInteractor.swift b/Example/Authenticator/Utils/Interactors/v1/ConsentsInteractor.swift index 2c24d9fc..ea82f217 100644 --- a/Example/Authenticator/Utils/Interactors/v1/ConsentsInteractor.swift +++ b/Example/Authenticator/Utils/Interactors/v1/ConsentsInteractor.swift @@ -22,6 +22,7 @@ import Foundation import SEAuthenticator +import SEAuthenticatorV2 import SEAuthenticatorCore struct ConsentsInteractor { @@ -37,14 +38,18 @@ struct ConsentsInteractor { entityId: consent.id ) - SEConsentsManager.revokeConsent( - data: data, - onSuccess: { _ in - success?() - }, - onFailure: { error in - failure?(error) - } - ) + if connection.isApiV2 { + SEConsentManagerV2.revokeConsent( + data: data, + onSuccess: { _ in success?() }, + onFailure: { error in failure?(error) } + ) + } else { + SEConsentManager.revokeConsent( + data: data, + onSuccess: { _ in success?() }, + onFailure: { error in failure?(error) } + ) + } } } diff --git a/Example/Authenticator/View Models/Connections/ConsentDetailViewModel.swift b/Example/Authenticator/View Models/Connections/ConsentDetailViewModel.swift index f3abf26a..be54eb9a 100644 --- a/Example/Authenticator/View Models/Connections/ConsentDetailViewModel.swift +++ b/Example/Authenticator/View Models/Connections/ConsentDetailViewModel.swift @@ -21,7 +21,7 @@ // import UIKit -import SEAuthenticator +import SEAuthenticatorCore protocol ConsentDetailViewModelEventsDelegate: class { func revoke(_ consent: SEConsentData, messageTitle: String, messageDescription: String, successMessage: String) diff --git a/Example/Authenticator/Views/Consents/ConsentSharedDataView.swift b/Example/Authenticator/Views/Consents/ConsentSharedDataView.swift index a0c16361..83879362 100644 --- a/Example/Authenticator/Views/Consents/ConsentSharedDataView.swift +++ b/Example/Authenticator/Views/Consents/ConsentSharedDataView.swift @@ -21,7 +21,7 @@ // import UIKit -import SEAuthenticator +import SEAuthenticatorCore private struct Layout { static let labelsSpacing: CGFloat = 10.0 diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/SEConsentData.swift b/SaltedgeAuthenticatorCore/Classes/API/Models/SEConsentData.swift similarity index 95% rename from SaltedgeAuthenticatorSDK/Classes/API/Models/SEConsentData.swift rename to SaltedgeAuthenticatorCore/Classes/API/Models/SEConsentData.swift index b25218e8..dbe76a0e 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/SEConsentData.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/Models/SEConsentData.swift @@ -1,8 +1,8 @@ // -// SEConsentData.swift +// SEConsentData // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2020 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -21,11 +21,10 @@ // import Foundation -import SEAuthenticatorCore public struct SEConsentData { public let id: String - public let userId: String + public var userId: String? public let connectionId: String public let tppName: String public let consentType: String @@ -36,14 +35,12 @@ public struct SEConsentData { public init?(_ dictionary: [String: Any], _ connectionId: String) { if let id = dictionary[SENetKeys.id] as? String, - let userId = dictionary[SENetKeys.userId] as? String, let tppName = dictionary[SENetKeys.tppName] as? String, let consentType = dictionary[SENetKeys.consentType] as? String, let accountsObjects = dictionary[SENetKeys.accounts] as? [[String: Any]], let createdAt = (dictionary[SENetKeys.createdAt] as? String)?.iso8601date, let expiresAt = (dictionary[SENetKeys.expiresAt] as? String)?.iso8601date { self.id = id - self.userId = userId self.tppName = tppName self.consentType = consentType self.createdAt = createdAt @@ -55,6 +52,10 @@ public struct SEConsentData { self.sharedData = SEConsentSharedData((dictionary[SENetKeys.sharedData] as? [String: Bool])) self.connectionId = connectionId + + if let userId = dictionary[SENetKeys.userId] as? String { + self.userId = userId + } } else { return nil } @@ -118,3 +119,4 @@ extension SEConsentSharedData: Equatable { return lhs.balance == rhs.balance && lhs.transactions == rhs.transactions } } + diff --git a/SaltedgeAuthenticatorCore/Classes/API/SEEncryptedData.swift b/SaltedgeAuthenticatorCore/Classes/API/SEEncryptedData.swift index 69bb1a39..e40d7467 100644 --- a/SaltedgeAuthenticatorCore/Classes/API/SEEncryptedData.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/SEEncryptedData.swift @@ -61,3 +61,28 @@ public struct SEEncryptedData: SEBaseEncryptedAuthorizationData, SerializableRes lhs.connectionId == rhs.connectionId } } + +public struct SEEncryptedDataResponse: SerializableResponse { + public var data: SEEncryptedData + + public init?(_ value: Any) { + if let response = (value as AnyObject)[SENetKeys.data] as? [String: Any], + let data = SEEncryptedData(response) { + self.data = data + } else { + return nil + } + } +} + +public struct SEEncryptedListResponse: SerializableResponse { + public var data: [SEEncryptedData] = [] + + public init?(_ value: Any) { + if let responses = (value as AnyObject)[SENetKeys.data] as? [[String: Any]] { + self.data = responses.compactMap { SEEncryptedData($0) } + } else { + return nil + } + } +} diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEConsentsManager.swift b/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEConsentManager.swift similarity index 91% rename from SaltedgeAuthenticatorSDK/Classes/API/Managers/SEConsentsManager.swift rename to SaltedgeAuthenticatorSDK/Classes/API/Managers/SEConsentManager.swift index baf5c7a1..bbdae354 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEConsentsManager.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEConsentManager.swift @@ -1,5 +1,5 @@ // -// SEConsentsManager.swift +// SEConsentManager.swift // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) // Copyright © 2020 Salt Edge Inc. @@ -23,14 +23,14 @@ import Foundation import SEAuthenticatorCore -public struct SEConsentsManager { +public struct SEConsentManager { public static func getEncryptedConsents( data: SEBaseAuthenticatedRequestData, onSuccess success: @escaping HTTPServiceSuccessClosure, onFailure failure: @escaping FailureBlock ) { HTTPService.execute( - request: SEConsentsRouter.list(data), + request: SEConsentRouter.list(data), success: success, failure: failure ) @@ -42,7 +42,7 @@ public struct SEConsentsManager { onFailure failure: @escaping FailureBlock ) { HTTPService.execute( - request: SEConsentsRouter.revoke(data), + request: SEConsentRouter.revoke(data), success: success, failure: failure ) diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/AuthorizationResponses.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/AuthorizationResponses.swift index 3e4e6feb..e854ee3e 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/AuthorizationResponses.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/AuthorizationResponses.swift @@ -23,27 +23,27 @@ import Foundation import SEAuthenticatorCore -public struct SEEncryptedDataResponse: SerializableResponse { - public var data: SEEncryptedData - - public init?(_ value: Any) { - if let response = (value as AnyObject)[SENetKeys.data] as? [String: Any], - let data = SEEncryptedData(response) { - self.data = data - } else { - return nil - } - } -} - -public struct SEEncryptedListResponse: SerializableResponse { - public var data: [SEEncryptedData] = [] - - public init?(_ value: Any) { - if let responses = (value as AnyObject)[SENetKeys.data] as? [[String: Any]] { - self.data = responses.compactMap { SEEncryptedData($0) } - } else { - return nil - } - } -} +//public struct SEEncryptedDataResponse: SerializableResponse { +// public var data: SEEncryptedData +// +// public init?(_ value: Any) { +// if let response = (value as AnyObject)[SENetKeys.data] as? [String: Any], +// let data = SEEncryptedData(response) { +// self.data = data +// } else { +// return nil +// } +// } +//} +// +//public struct SEEncryptedListResponse: SerializableResponse { +// public var data: [SEEncryptedData] = [] +// +// public init?(_ value: Any) { +// if let responses = (value as AnyObject)[SENetKeys.data] as? [[String: Any]] { +// self.data = responses.compactMap { SEEncryptedData($0) } +// } else { +// return nil +// } +// } +//} diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEConsentsRouter.swift b/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEConsentRouter.swift similarity index 98% rename from SaltedgeAuthenticatorSDK/Classes/API/Routers/SEConsentsRouter.swift rename to SaltedgeAuthenticatorSDK/Classes/API/Routers/SEConsentRouter.swift index baca59b9..82d4491f 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEConsentsRouter.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEConsentRouter.swift @@ -1,5 +1,5 @@ // -// SEConsentsRouter.swift +// SEConsentRouter.swift // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) // Copyright © 2020 Salt Edge Inc. @@ -23,7 +23,7 @@ import Foundation import SEAuthenticatorCore -enum SEConsentsRouter: Routable { +enum SEConsentRouter: Routable { case list(SEBaseAuthenticatedRequestData) case revoke(SEBaseAuthenticatedWithIdRequestData) diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConsentManagerV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConsentManagerV2.swift new file mode 100644 index 00000000..38546b9d --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConsentManagerV2.swift @@ -0,0 +1,50 @@ +// +// SEConsentManagerV2 +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +public struct SEConsentManagerV2 { + public static func getEncryptedConsents( + data: SEBaseAuthenticatedRequestData, + onSuccess success: @escaping HTTPServiceSuccessClosure, + onFailure failure: @escaping FailureBlock + ) { + HTTPService.execute( + request: SEConsentRouter.list(data), + success: success, + failure: failure + ) + } + + public static func revokeConsent( + data: SEBaseAuthenticatedWithIdRequestData, + onSuccess success: @escaping HTTPServiceSuccessClosure, + onFailure failure: @escaping FailureBlock + ) { + HTTPService.execute( + request: SEConsentRouter.revoke(data), + success: success, + failure: failure + ) + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConsentResponseV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConsentResponseV2.swift new file mode 100644 index 00000000..5a0305fc --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConsentResponseV2.swift @@ -0,0 +1,38 @@ +// +// SERevokeConsentResponseV2 +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +public struct SERevokeConsentResponseV2: SerializableResponse { + public let id: String + + public init?(_ value: Any) { + if let dict = value as? [String: Any], + let data = dict[SENetKeys.data] as? [String: Any], + let id = data[SENetKeys.id] as? String { + self.id = id + } else { + return nil + } + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConsentRouterV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConsentRouterV2.swift new file mode 100644 index 00000000..4c8800ad --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConsentRouterV2.swift @@ -0,0 +1,78 @@ +// +// SEConsentRouterV2 +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation + +import Foundation +import SEAuthenticatorCore + +enum SEConsentRouter: Routable { + case list(SEBaseAuthenticatedRequestData) + case revoke(SEBaseAuthenticatedWithIdRequestData) + + var method: HTTPMethod { + switch self { + case .list: return .get + case .revoke: return .delete + } + } + + var encoding: Encoding { + switch self { + case .list, .revoke: return .url + } + } + + var url: URL { + switch self { + case .list(let data): + return data.url.appendingPathComponent( + SENetPathBuilder(for: .consents, version: 2).path + ) + case .revoke(let data): + return data.url.appendingPathComponent( + "\(SENetPathBuilder(for: .consents, version: 2).path)/\(data.entityId)/revoke" + ) + } + } + + var parameters: [String: Any]? { + switch self { + case .list: return nil + case .revoke: + return RequestParametersBuilder.expirationTimeParameters + } + } + + var headers: [String: String]? { + switch self { + case .list(let data): + return Headers.authorizedRequestHeaders(token: data.accessToken) + case .revoke(let data): + return Headers.signedRequestHeaders( + token: data.accessToken, + payloadParams: RequestParametersBuilder.expirationTimeParameters, + connectionGuid: data.connectionGuid + ) + } + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift b/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift index d9f0e74c..c74a59e5 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift @@ -93,4 +93,11 @@ struct RequestParametersBuilder { ParametersKeys.exp: Date().addingTimeInterval(5.0 * 60.0).utcSeconds ] } + + static var expirationTimeParameters: [String: Any] { + return [ + ParametersKeys.data: [:], + ParametersKeys.exp: Date().addingTimeInterval(5.0 * 60.0).utcSeconds + ] + } } From 631464aba0fca8bc846814ed9c58903995dcc350 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Thu, 15 Jul 2021 09:53:03 +0300 Subject: [PATCH 048/110] removed unneeded file --- .../Responses/AuthorizationResponses.swift | 49 ------------------- 1 file changed, 49 deletions(-) delete mode 100644 SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/AuthorizationResponses.swift diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/AuthorizationResponses.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/AuthorizationResponses.swift deleted file mode 100644 index e854ee3e..00000000 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/AuthorizationResponses.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// AuthorizationResponses.swift -// This file is part of the Salt Edge Authenticator distribution -// (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 3 or later. -// -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -// For the additional permissions granted for Salt Edge Authenticator -// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md -// - -import Foundation -import SEAuthenticatorCore - -//public struct SEEncryptedDataResponse: SerializableResponse { -// public var data: SEEncryptedData -// -// public init?(_ value: Any) { -// if let response = (value as AnyObject)[SENetKeys.data] as? [String: Any], -// let data = SEEncryptedData(response) { -// self.data = data -// } else { -// return nil -// } -// } -//} -// -//public struct SEEncryptedListResponse: SerializableResponse { -// public var data: [SEEncryptedData] = [] -// -// public init?(_ value: Any) { -// if let responses = (value as AnyObject)[SENetKeys.data] as? [[String: Any]] { -// self.data = responses.compactMap { SEEncryptedData($0) } -// } else { -// return nil -// } -// } -//} From f24a147c056c203c272f83d1956851035aab6c53 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Wed, 28 Jul 2021 11:43:24 +0300 Subject: [PATCH 049/110] fixed consents v2 fetching and revoke --- .../Authenticator.xcodeproj/project.pbxproj | 2 +- .../SEEncryptedDataExtensions.swift | 2 +- .../Base/CollectionsInteractor.swift | 25 +++++++++++++++---- .../{v1 => Base}/ConsentsInteractor.swift | 5 ++-- .../SEBaseEncryptedAuthorizationData.swift | 1 + .../Classes/API/Models/SEConsentData.swift | 13 ++++++---- .../Classes/API/SEEncryptedData.swift | 10 +++++--- .../API/Managers/SEConsentManagerV2.swift | 4 ++- .../API/Routers/SEConsentRouterV2.swift | 16 ++++++------ .../API/SEEncryptedAuthorizationData.swift | 1 + 10 files changed, 53 insertions(+), 26 deletions(-) rename Example/Authenticator/Utils/Interactors/{v1 => Base}/ConsentsInteractor.swift (91%) diff --git a/Example/Authenticator.xcodeproj/project.pbxproj b/Example/Authenticator.xcodeproj/project.pbxproj index bcddeb46..dee0bf0c 100644 --- a/Example/Authenticator.xcodeproj/project.pbxproj +++ b/Example/Authenticator.xcodeproj/project.pbxproj @@ -993,7 +993,6 @@ isa = PBXGroup; children = ( 9DCED82D22CE18270050ED3C /* ConnectionsInteractor.swift */, - 9D98747B24B390F200EE89BB /* ConsentsInteractor.swift */, ); path = v1; sourceTree = ""; @@ -1353,6 +1352,7 @@ 9DD155F226613CEA00AEFA0D /* BaseConnectionsInteractor.swift */, 9DCED82C22CE18270050ED3C /* AuthorizationsInteractor.swift */, 95C6781E24A9FD8B0088A9CF /* CollectionsInteractor.swift */, + 9D98747B24B390F200EE89BB /* ConsentsInteractor.swift */, ); path = Base; sourceTree = ""; diff --git a/Example/Authenticator/Utils/Extensions/SEEncryptedDataExtensions.swift b/Example/Authenticator/Utils/Extensions/SEEncryptedDataExtensions.swift index 59160f75..088d657e 100644 --- a/Example/Authenticator/Utils/Extensions/SEEncryptedDataExtensions.swift +++ b/Example/Authenticator/Utils/Extensions/SEEncryptedDataExtensions.swift @@ -59,7 +59,7 @@ extension SEBaseEncryptedAuthorizationData { var decryptedConsentData: SEConsentData? { if let connectionId = self.connectionId, let decryptedDictionary = self.decryptedDictionary { - return SEConsentData(decryptedDictionary, connectionId) + return SEConsentData(decryptedDictionary, entityId, connectionId) } return nil } diff --git a/Example/Authenticator/Utils/Interactors/Base/CollectionsInteractor.swift b/Example/Authenticator/Utils/Interactors/Base/CollectionsInteractor.swift index f1a5466f..47924ac0 100644 --- a/Example/Authenticator/Utils/Interactors/Base/CollectionsInteractor.swift +++ b/Example/Authenticator/Utils/Interactors/Base/CollectionsInteractor.swift @@ -145,11 +145,26 @@ enum CollectionsInteractor { ) } case .consents: - SEConsentManager.getEncryptedConsents( - data: requestData, - onSuccess: { response in onSuccess(data: response.data) }, - onFailure: { error in onFailure(error: error, connection: connection) } - ) + if connection.isApiV2 { + SEConsentManagerV2.getEncryptedConsents( + data: requestData, + onSuccess: { response in + onSuccess(data: response.data) + }, onFailure: { error in + onFailure(error: error, connection: connection) + } + ) + } else { + SEConsentManager.getEncryptedConsents( + data: requestData, + onSuccess: { response in + onSuccess(data: response.data) + }, + onFailure: { error in + onFailure(error: error, connection: connection) + } + ) + } } } } diff --git a/Example/Authenticator/Utils/Interactors/v1/ConsentsInteractor.swift b/Example/Authenticator/Utils/Interactors/Base/ConsentsInteractor.swift similarity index 91% rename from Example/Authenticator/Utils/Interactors/v1/ConsentsInteractor.swift rename to Example/Authenticator/Utils/Interactors/Base/ConsentsInteractor.swift index ea82f217..740439e6 100644 --- a/Example/Authenticator/Utils/Interactors/v1/ConsentsInteractor.swift +++ b/Example/Authenticator/Utils/Interactors/Base/ConsentsInteractor.swift @@ -28,14 +28,15 @@ import SEAuthenticatorCore struct ConsentsInteractor { static func revoke(_ consent: SEConsentData, success: (() -> ())? = nil, failure: ((String) -> ())? = nil) { guard let connection = ConnectionsCollector.with(id: consent.connectionId), - let baseUrl = connection.baseUrl else { failure?(l10n(.somethingWentWrong)); return } + let baseUrl = connection.baseUrl, + let consentId = consent.id else { failure?(l10n(.somethingWentWrong)); return } let data = SEBaseAuthenticatedWithIdRequestData( url: baseUrl, connectionGuid: connection.guid, accessToken: connection.accessToken, appLanguage: UserDefaultsHelper.applicationLanguage, - entityId: consent.id + entityId: consentId ) if connection.isApiV2 { diff --git a/SaltedgeAuthenticatorCore/Classes/API/Models/SEBaseEncryptedAuthorizationData.swift b/SaltedgeAuthenticatorCore/Classes/API/Models/SEBaseEncryptedAuthorizationData.swift index 48692b2f..76ce0251 100644 --- a/SaltedgeAuthenticatorCore/Classes/API/Models/SEBaseEncryptedAuthorizationData.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/Models/SEBaseEncryptedAuthorizationData.swift @@ -27,4 +27,5 @@ public protocol SEBaseEncryptedAuthorizationData { var key: String { get } var iv: String { get } var connectionId: String? { get } + var entityId: String? { get } } diff --git a/SaltedgeAuthenticatorCore/Classes/API/Models/SEConsentData.swift b/SaltedgeAuthenticatorCore/Classes/API/Models/SEConsentData.swift index dbe76a0e..990fa6ed 100644 --- a/SaltedgeAuthenticatorCore/Classes/API/Models/SEConsentData.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/Models/SEConsentData.swift @@ -23,7 +23,7 @@ import Foundation public struct SEConsentData { - public let id: String + public var id: String? public var userId: String? public let connectionId: String public let tppName: String @@ -33,14 +33,12 @@ public struct SEConsentData { public let createdAt: Date public let expiresAt: Date - public init?(_ dictionary: [String: Any], _ connectionId: String) { - if let id = dictionary[SENetKeys.id] as? String, - let tppName = dictionary[SENetKeys.tppName] as? String, + public init?(_ dictionary: [String: Any], _ entityId: String?, _ connectionId: String) { + if let tppName = dictionary[SENetKeys.tppName] as? String, let consentType = dictionary[SENetKeys.consentType] as? String, let accountsObjects = dictionary[SENetKeys.accounts] as? [[String: Any]], let createdAt = (dictionary[SENetKeys.createdAt] as? String)?.iso8601date, let expiresAt = (dictionary[SENetKeys.expiresAt] as? String)?.iso8601date { - self.id = id self.tppName = tppName self.consentType = consentType self.createdAt = createdAt @@ -56,6 +54,11 @@ public struct SEConsentData { if let userId = dictionary[SENetKeys.userId] as? String { self.userId = userId } + if let entityId = entityId { + self.id = entityId + } else if let id = dictionary[SENetKeys.id] as? String { + self.id = id + } } else { return nil } diff --git a/SaltedgeAuthenticatorCore/Classes/API/SEEncryptedData.swift b/SaltedgeAuthenticatorCore/Classes/API/SEEncryptedData.swift index e40d7467..85ac8735 100644 --- a/SaltedgeAuthenticatorCore/Classes/API/SEEncryptedData.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/SEEncryptedData.swift @@ -29,6 +29,7 @@ public struct SEEncryptedData: SEBaseEncryptedAuthorizationData, SerializableRes public let key: String public let iv: String public var connectionId: String? + public var entityId: String? public init(data: String, key: String, iv: String) { self.data = data @@ -40,14 +41,17 @@ public struct SEEncryptedData: SEBaseEncryptedAuthorizationData, SerializableRes if let dict = value as? [String: Any], let data = dict[SENetKeys.data] as? String, let key = dict[SENetKeys.key] as? String, - let iv = dict[SENetKeys.iv] as? String, - let algorithm = dict[SENetKeys.algorithm] as? String, - algorithm == defaultAlgorithm { + let iv = dict[SENetKeys.iv] as? String { self.data = data self.key = key self.iv = iv + if let entityId = dict[SENetKeys.id] as? Int { + self.entityId = "\(entityId)" + } if let connectionId = dict[SENetKeys.connectionId] as? String { self.connectionId = connectionId + } else if let connectionId = dict[SENetKeys.connectionId] as? Int { // NOTE: connection_id in v2 is integer + self.connectionId = "\(connectionId)" } } else { return nil diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConsentManagerV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConsentManagerV2.swift index 38546b9d..349f90b0 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConsentManagerV2.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConsentManagerV2.swift @@ -41,8 +41,10 @@ public struct SEConsentManagerV2 { onSuccess success: @escaping HTTPServiceSuccessClosure, onFailure failure: @escaping FailureBlock ) { + let parameters = RequestParametersBuilder.expirationTimeParameters + HTTPService.execute( - request: SEConsentRouter.revoke(data), + request: SEConsentRouter.revoke(data, parameters), success: success, failure: failure ) diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConsentRouterV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConsentRouterV2.swift index 4c8800ad..dbde07e0 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConsentRouterV2.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConsentRouterV2.swift @@ -27,18 +27,19 @@ import SEAuthenticatorCore enum SEConsentRouter: Routable { case list(SEBaseAuthenticatedRequestData) - case revoke(SEBaseAuthenticatedWithIdRequestData) + case revoke(SEBaseAuthenticatedWithIdRequestData, [String: Any]) var method: HTTPMethod { switch self { case .list: return .get - case .revoke: return .delete + case .revoke: return .put } } var encoding: Encoding { switch self { - case .list, .revoke: return .url + case .list: return .url + case .revoke: return .json } } @@ -48,7 +49,7 @@ enum SEConsentRouter: Routable { return data.url.appendingPathComponent( SENetPathBuilder(for: .consents, version: 2).path ) - case .revoke(let data): + case .revoke(let data, _): return data.url.appendingPathComponent( "\(SENetPathBuilder(for: .consents, version: 2).path)/\(data.entityId)/revoke" ) @@ -58,8 +59,7 @@ enum SEConsentRouter: Routable { var parameters: [String: Any]? { switch self { case .list: return nil - case .revoke: - return RequestParametersBuilder.expirationTimeParameters + case let .revoke(_, parameters): return parameters } } @@ -67,10 +67,10 @@ enum SEConsentRouter: Routable { switch self { case .list(let data): return Headers.authorizedRequestHeaders(token: data.accessToken) - case .revoke(let data): + case let .revoke(data, parameters): return Headers.signedRequestHeaders( token: data.accessToken, - payloadParams: RequestParametersBuilder.expirationTimeParameters, + payloadParams: parameters, connectionGuid: data.connectionGuid ) } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/SEEncryptedAuthorizationData.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/SEEncryptedAuthorizationData.swift index 11ef7b4f..37ef8459 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/SEEncryptedAuthorizationData.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/SEEncryptedAuthorizationData.swift @@ -30,6 +30,7 @@ public struct SEEncryptedAuthorizationData: SEBaseEncryptedAuthorizationData, Se public let iv: String public let status: AuthorizationStatus public var connectionId: String? + public var entityId: String? public init?(_ value: Any) { if let dict = value as? [String: Any], From 856b94b4b00756e22372d19fb96ae186522a8d0e Mon Sep 17 00:00:00 2001 From: mnewlive Date: Wed, 28 Jul 2021 18:41:11 +0300 Subject: [PATCH 050/110] Modified README.md --- .../Authenticator.xcodeproj/project.pbxproj | 2 +- Example/Podfile.lock | 155 +++++++++--------- README.md | 22 +++ 3 files changed, 100 insertions(+), 79 deletions(-) diff --git a/Example/Authenticator.xcodeproj/project.pbxproj b/Example/Authenticator.xcodeproj/project.pbxproj index bcddeb46..4ad7b519 100644 --- a/Example/Authenticator.xcodeproj/project.pbxproj +++ b/Example/Authenticator.xcodeproj/project.pbxproj @@ -1730,7 +1730,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"error: SwiftLint does not exist, download it from https://github.com/realm/SwiftLint\"\n exit 1\nfi\n"; + shellScript = "if test -d \"/opt/homebrew/bin/\"; then\n PATH=\"/opt/homebrew/bin/:${PATH}\"\nfi\n\nexport PATH\n\nif which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; }; 9DCF6EBF252DE4F900D9182A /* ShellScript */ = { isa = PBXShellScriptBuildPhase; diff --git a/Example/Podfile.lock b/Example/Podfile.lock index 9fa61d91..296c5830 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -1,90 +1,89 @@ PODS: - - CryptoSwift (1.4.0) - - Firebase/Analytics (7.11.0): + - CryptoSwift (1.4.1) + - Firebase/Analytics (8.4.0): - Firebase/Core - - Firebase/Core (7.11.0): + - Firebase/Core (8.4.0): - Firebase/CoreOnly - - FirebaseAnalytics (~> 7.11.0) - - Firebase/CoreOnly (7.11.0): - - FirebaseCore (= 7.11.0) - - Firebase/Crashlytics (7.11.0): + - FirebaseAnalytics (~> 8.4.0) + - Firebase/CoreOnly (8.4.0): + - FirebaseCore (= 8.4.0) + - Firebase/Crashlytics (8.4.0): - Firebase/CoreOnly - - FirebaseCrashlytics (~> 7.11.0) - - FirebaseAnalytics (7.11.0): - - FirebaseAnalytics/AdIdSupport (= 7.11.0) - - FirebaseCore (~> 7.0) - - FirebaseInstallations (~> 7.0) - - GoogleUtilities/AppDelegateSwizzler (~> 7.0) - - GoogleUtilities/MethodSwizzler (~> 7.0) - - GoogleUtilities/Network (~> 7.0) - - "GoogleUtilities/NSData+zlib (~> 7.0)" + - FirebaseCrashlytics (~> 8.4.0) + - FirebaseAnalytics (8.4.0): + - FirebaseAnalytics/AdIdSupport (= 8.4.0) + - FirebaseCore (~> 8.0) + - FirebaseInstallations (~> 8.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.4) + - GoogleUtilities/MethodSwizzler (~> 7.4) + - GoogleUtilities/Network (~> 7.4) + - "GoogleUtilities/NSData+zlib (~> 7.4)" - nanopb (~> 2.30908.0) - - FirebaseAnalytics/AdIdSupport (7.11.0): - - FirebaseAnalytics/Base (= 7.11.0) - - FirebaseCore (~> 7.0) - - FirebaseInstallations (~> 7.0) - - GoogleAppMeasurement/AdIdSupport (= 7.11.0) - - GoogleUtilities/AppDelegateSwizzler (~> 7.0) - - GoogleUtilities/MethodSwizzler (~> 7.0) - - GoogleUtilities/Network (~> 7.0) - - "GoogleUtilities/NSData+zlib (~> 7.0)" + - FirebaseAnalytics/AdIdSupport (8.4.0): + - FirebaseCore (~> 8.0) + - FirebaseInstallations (~> 8.0) + - GoogleAppMeasurement (= 8.4.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.4) + - GoogleUtilities/MethodSwizzler (~> 7.4) + - GoogleUtilities/Network (~> 7.4) + - "GoogleUtilities/NSData+zlib (~> 7.4)" - nanopb (~> 2.30908.0) - - FirebaseAnalytics/Base (7.11.0): - - FirebaseCore (~> 7.0) - - FirebaseInstallations (~> 7.0) - - GoogleUtilities/AppDelegateSwizzler (~> 7.0) - - GoogleUtilities/MethodSwizzler (~> 7.0) - - GoogleUtilities/Network (~> 7.0) - - "GoogleUtilities/NSData+zlib (~> 7.0)" + - FirebaseCore (8.4.0): + - FirebaseCoreDiagnostics (~> 8.0) + - GoogleUtilities/Environment (~> 7.4) + - GoogleUtilities/Logger (~> 7.4) + - FirebaseCoreDiagnostics (8.4.0): + - GoogleDataTransport (~> 9.0) + - GoogleUtilities/Environment (~> 7.4) + - GoogleUtilities/Logger (~> 7.4) - nanopb (~> 2.30908.0) - - FirebaseCore (7.11.0): - - FirebaseCoreDiagnostics (~> 7.4) - - GoogleUtilities/Environment (~> 7.0) - - GoogleUtilities/Logger (~> 7.0) - - FirebaseCoreDiagnostics (7.11.0): - - GoogleDataTransport (~> 8.4) - - GoogleUtilities/Environment (~> 7.0) - - GoogleUtilities/Logger (~> 7.0) + - FirebaseCrashlytics (8.4.0): + - FirebaseCore (~> 8.0) + - FirebaseInstallations (~> 8.0) + - GoogleDataTransport (~> 9.0) + - GoogleUtilities/Environment (~> 7.4) - nanopb (~> 2.30908.0) - - FirebaseCrashlytics (7.11.0): - - FirebaseCore (~> 7.0) - - FirebaseInstallations (~> 7.0) - - GoogleDataTransport (~> 8.4) + - PromisesObjC (< 3.0, >= 1.2) + - FirebaseInstallations (8.4.0): + - FirebaseCore (~> 8.0) + - GoogleUtilities/Environment (~> 7.4) + - GoogleUtilities/UserDefaults (~> 7.4) + - PromisesObjC (< 3.0, >= 1.2) + - GoogleAppMeasurement (8.4.0): + - GoogleAppMeasurement/AdIdSupport (= 8.4.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.4) + - GoogleUtilities/MethodSwizzler (~> 7.4) + - GoogleUtilities/Network (~> 7.4) + - "GoogleUtilities/NSData+zlib (~> 7.4)" - nanopb (~> 2.30908.0) - - PromisesObjC (~> 1.2) - - FirebaseInstallations (7.11.0): - - FirebaseCore (~> 7.0) - - GoogleUtilities/Environment (~> 7.0) - - GoogleUtilities/UserDefaults (~> 7.0) - - PromisesObjC (~> 1.2) - - GoogleAppMeasurement/AdIdSupport (7.11.0): - - GoogleUtilities/AppDelegateSwizzler (~> 7.0) - - GoogleUtilities/MethodSwizzler (~> 7.0) - - GoogleUtilities/Network (~> 7.0) - - "GoogleUtilities/NSData+zlib (~> 7.0)" + - GoogleAppMeasurement/AdIdSupport (8.4.0): + - GoogleUtilities/AppDelegateSwizzler (~> 7.4) + - GoogleUtilities/MethodSwizzler (~> 7.4) + - GoogleUtilities/Network (~> 7.4) + - "GoogleUtilities/NSData+zlib (~> 7.4)" - nanopb (~> 2.30908.0) - - GoogleDataTransport (8.4.0): + - GoogleDataTransport (9.1.0): - GoogleUtilities/Environment (~> 7.2) - nanopb (~> 2.30908.0) - - PromisesObjC (~> 1.2) - - GoogleUtilities/AppDelegateSwizzler (7.4.0): + - PromisesObjC (< 3.0, >= 1.2) + - GoogleUtilities/AppDelegateSwizzler (7.5.0): - GoogleUtilities/Environment - GoogleUtilities/Logger - GoogleUtilities/Network - - GoogleUtilities/Environment (7.4.0): - - PromisesObjC (~> 1.2) - - GoogleUtilities/Logger (7.4.0): + - GoogleUtilities/Environment (7.5.0): + - PromisesObjC (< 3.0, >= 1.2) + - GoogleUtilities/Logger (7.5.0): - GoogleUtilities/Environment - - GoogleUtilities/MethodSwizzler (7.4.0): + - GoogleUtilities/MethodSwizzler (7.5.0): - GoogleUtilities/Logger - - GoogleUtilities/Network (7.4.0): + - GoogleUtilities/Network (7.5.0): - GoogleUtilities/Logger - "GoogleUtilities/NSData+zlib" - GoogleUtilities/Reachability - - "GoogleUtilities/NSData+zlib (7.4.0)" - - GoogleUtilities/Reachability (7.4.0): + - "GoogleUtilities/NSData+zlib (7.5.0)" + - GoogleUtilities/Reachability (7.5.0): - GoogleUtilities/Logger - - GoogleUtilities/UserDefaults (7.4.0): + - GoogleUtilities/UserDefaults (7.5.0): - GoogleUtilities/Logger - JOSESwift (2.4.0) - nanopb (2.30908.0): @@ -93,7 +92,7 @@ PODS: - nanopb/decode (2.30908.0) - nanopb/encode (2.30908.0) - Nimble (9.2.0) - - PromisesObjC (1.2.12) + - PromisesObjC (2.0.0) - Quick (4.0.0) - ReachabilitySwift (5.0.0) - Realm (5.5.1): @@ -162,20 +161,20 @@ EXTERNAL SOURCES: :path: "../SaltedgeAuthenticatorSDKv2" SPEC CHECKSUMS: - CryptoSwift: 7cc902df1784de3b389a387756c7d710f197730c - Firebase: c121feb35e4126c0b355e3313fa9b487d47319fd - FirebaseAnalytics: cd3bd84d722a24a8923918af8af8e5236f615d77 - FirebaseCore: 907447d8917a4d3eb0cce2829c5a0ad21d90b432 - FirebaseCoreDiagnostics: 68ad972f99206cef818230f3f3179d52ccfb7f8c - FirebaseCrashlytics: 272b675aa9d1e9bae1f9e1449fcc1f2cf6042806 - FirebaseInstallations: a58d4f72ec5861840b84df489f2668d970df558a - GoogleAppMeasurement: fd19169c3034975cb934e865e5667bfdce59df7f - GoogleDataTransport: cd9db2180fcecd8da1b561aea31e3e56cf834aa7 - GoogleUtilities: 284cddc7fffc14ae1907efb6f78ab95c1fccaedc + CryptoSwift: 0bc800a7e6a24c4fc9ebeab97d44b0d5f73a78bd + Firebase: 54cdc8bc9c9b3de54f43dab86e62f5a76b47034f + FirebaseAnalytics: 4751d6a49598a2b58da678cc07df696bcd809ab9 + FirebaseCore: 31f389c37ac1ea52454a53d3081f2d7019485a4a + FirebaseCoreDiagnostics: cad03be1904b975f845e632f2720c3337da27faf + FirebaseCrashlytics: c9eb562b2f6bd5ee5e880144fd5ef1bfe46c5dc5 + FirebaseInstallations: 1585729afc787877763208c2088ed84221161f77 + GoogleAppMeasurement: 6b6a08fd9c71f4dbc89e0e812acca81d797aa342 + GoogleDataTransport: 85fd18ff3019bb85d3f2c551d04c481dedf71fc9 + GoogleUtilities: eea970f4a389963963bffe8d8fabe43540678b9c JOSESwift: 7ff178bb9173ff42c6e990929a9f2fa702a34f69 nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96 Nimble: 4f4a345c80b503b3ea13606a4f98405974ee4d0b - PromisesObjC: 3113f7f76903778cf4a0586bd1ab89329a0b7b97 + PromisesObjC: 68159ce6952d93e17b2dfe273b8c40907db5ba58 Quick: 6473349e43b9271a8d43839d9ba1c442ed1b7ac4 ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 Realm: c2ffe0667f9c98c9ba65f608ba85684d39826145 diff --git a/README.md b/README.md index fee2af60..f2f9fc73 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,21 @@ You can find related source code: ```bash brew install swiftlint ``` + If `brew install swiftlint` fails on M1 Macs, go to the project settings(`Build Phases`) + and add it to the Run Script next section: + ``` + if test -d "/opt/homebrew/bin/"; then + PATH="/opt/homebrew/bin/:${PATH}" + fi + + export PATH + + if which swiftlint >/dev/null; then + swiftlint + else + echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint" + fi + ``` ## SDK installation via [CocoaPods](https://cocoapods.org) @@ -76,6 +91,13 @@ or build from source code. 1. Move to project directory `sca-authenticator-ios/Example` 1. Command in terminal: `bundle install` (To install all required gems) 1. Command in terminal: `pod install` (To install all required pods) + + If `pod install` fails on M1 Macs, do next: + ``` + sudo arch -x86_64 gem install ffi + arch -x86_64 pod update + ``` + Also make sure you set the `arm64` values in the "Build Settings" -> "Architectures" -> "Excluded Architectures". 1. Open project's workspace file in Xcode (`Example/Authenticator.xcworkspace`) 1. Create `application.plist` configuration file using `application.example.plist` 1. If you have intent to use Firebase Crashlytics then generate `GoogleService-info.plist` and add it to project. From 2f337ace0080e0cba4383d1437150f67824fd08d Mon Sep 17 00:00:00 2001 From: mnewlive Date: Thu, 29 Jul 2021 12:46:13 +0300 Subject: [PATCH 051/110] Removed info about swithLint script --- README.md | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/README.md b/README.md index f2f9fc73..985d8f23 100644 --- a/README.md +++ b/README.md @@ -53,21 +53,6 @@ You can find related source code: ```bash brew install swiftlint ``` - If `brew install swiftlint` fails on M1 Macs, go to the project settings(`Build Phases`) - and add it to the Run Script next section: - ``` - if test -d "/opt/homebrew/bin/"; then - PATH="/opt/homebrew/bin/:${PATH}" - fi - - export PATH - - if which swiftlint >/dev/null; then - swiftlint - else - echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint" - fi - ``` ## SDK installation via [CocoaPods](https://cocoapods.org) From bc1be721d9d4077c55f6b7baed28e6e19df76ece Mon Sep 17 00:00:00 2001 From: mnewlive Date: Thu, 29 Jul 2021 16:57:01 +0300 Subject: [PATCH 052/110] Menu adjustments --- .../Localization/en.lproj/Authenticator.strings | 1 + Example/Authenticator/Utils/Helpers/Localizations.swift | 1 + .../Connection/ConnectionsViewController.swift | 1 + .../Connections/ConnectionCellViewModel.swift | 9 +++++++++ 4 files changed, 12 insertions(+) diff --git a/Example/Authenticator/Supporting Files/Localization/en.lproj/Authenticator.strings b/Example/Authenticator/Supporting Files/Localization/en.lproj/Authenticator.strings index f1cd09b8..81b5db9a 100644 --- a/Example/Authenticator/Supporting Files/Localization/en.lproj/Authenticator.strings +++ b/Example/Authenticator/Supporting Files/Localization/en.lproj/Authenticator.strings @@ -23,6 +23,7 @@ "actions.proceed" = "Proceed"; "action.connect_provider" = "Connect Provider"; "actions.connect" = "Connect"; +"actions.view_id" = "ID:"; "actions.reconnect" = "Reconnect"; "actions.remove" = "Remove"; "actions.rename" = "Rename"; diff --git a/Example/Authenticator/Utils/Helpers/Localizations.swift b/Example/Authenticator/Utils/Helpers/Localizations.swift index 160cbf5c..a01acdff 100644 --- a/Example/Authenticator/Utils/Helpers/Localizations.swift +++ b/Example/Authenticator/Utils/Helpers/Localizations.swift @@ -224,6 +224,7 @@ enum Localizations: String, Localizable { case contactSupport = "in_app.settings.contact_support" case reportAnIssue = "actions.report_an_issue" case viewConsents = "actions.view_consents" + case id = "actions.view_id" // MARK: - Main Menu Options case viewConnections = "actions.view_connections" diff --git a/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift b/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift index 17bf8c04..c1d2cbc7 100644 --- a/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift +++ b/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift @@ -196,6 +196,7 @@ extension ConnectionsViewController: Layoutable { // MARK: - ConnectionCellEventsDelegate extension ConnectionsViewController: ConnectionCellEventsDelegate { + func renamePressed(id: String) { viewModel.updateName(by: id) } diff --git a/Example/Authenticator/View Models/Connections/ConnectionCellViewModel.swift b/Example/Authenticator/View Models/Connections/ConnectionCellViewModel.swift index d1ce1da0..04c28aaa 100644 --- a/Example/Authenticator/View Models/Connections/ConnectionCellViewModel.swift +++ b/Example/Authenticator/View Models/Connections/ConnectionCellViewModel.swift @@ -123,6 +123,15 @@ class ConnectionCellViewModel { } ) } + + actions.append( + UIAction( + title: "\(l10n(.id)) \(strongSelf.connection.id)", + image: UIImage(systemName: "info.circle"), + attributes: .disabled) { _ in + return + } + ) return UIMenu(title: "", image: nil, identifier: nil, options: .destructive, children: actions) } From 5dc815f1feba3a16f61891df903e3c5d4af92326 Mon Sep 17 00:00:00 2001 From: mnewlive Date: Thu, 29 Jul 2021 17:13:58 +0300 Subject: [PATCH 053/110] Minor changes --- .../Connection/ConnectionsViewController.swift | 1 - .../View Models/Connections/ConnectionCellViewModel.swift | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift b/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift index c1d2cbc7..17bf8c04 100644 --- a/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift +++ b/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift @@ -196,7 +196,6 @@ extension ConnectionsViewController: Layoutable { // MARK: - ConnectionCellEventsDelegate extension ConnectionsViewController: ConnectionCellEventsDelegate { - func renamePressed(id: String) { viewModel.updateName(by: id) } diff --git a/Example/Authenticator/View Models/Connections/ConnectionCellViewModel.swift b/Example/Authenticator/View Models/Connections/ConnectionCellViewModel.swift index 04c28aaa..93974328 100644 --- a/Example/Authenticator/View Models/Connections/ConnectionCellViewModel.swift +++ b/Example/Authenticator/View Models/Connections/ConnectionCellViewModel.swift @@ -128,9 +128,7 @@ class ConnectionCellViewModel { UIAction( title: "\(l10n(.id)) \(strongSelf.connection.id)", image: UIImage(systemName: "info.circle"), - attributes: .disabled) { _ in - return - } + attributes: .disabled) { _ in return } ) return UIMenu(title: "", image: nil, identifier: nil, options: .destructive, children: actions) From 001be756f6ecc0d4c641928acb6d6b1ae64c25fb Mon Sep 17 00:00:00 2001 From: mnewlive Date: Thu, 29 Jul 2021 17:22:47 +0300 Subject: [PATCH 054/110] Code style changes --- .../View Models/Connections/ConnectionCellViewModel.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Example/Authenticator/View Models/Connections/ConnectionCellViewModel.swift b/Example/Authenticator/View Models/Connections/ConnectionCellViewModel.swift index 93974328..4bd01123 100644 --- a/Example/Authenticator/View Models/Connections/ConnectionCellViewModel.swift +++ b/Example/Authenticator/View Models/Connections/ConnectionCellViewModel.swift @@ -128,7 +128,8 @@ class ConnectionCellViewModel { UIAction( title: "\(l10n(.id)) \(strongSelf.connection.id)", image: UIImage(systemName: "info.circle"), - attributes: .disabled) { _ in return } + attributes: .disabled + ) { _ in return } ) return UIMenu(title: "", image: nil, identifier: nil, options: .destructive, children: actions) From 2712c8ef3380822ba51cd272473c186230855500 Mon Sep 17 00:00:00 2001 From: mnewlive Date: Mon, 2 Aug 2021 16:17:10 +0300 Subject: [PATCH 055/110] Revoke action if no internet --- .../Localization/en.lproj/Authenticator.strings | 1 + .../Utils/Helpers/Localizations.swift | 1 + .../Connection/ConnectViewController.swift | 1 - .../Connection/ConnectionsViewController.swift | 15 +++++++++++++++ 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Example/Authenticator/Supporting Files/Localization/en.lproj/Authenticator.strings b/Example/Authenticator/Supporting Files/Localization/en.lproj/Authenticator.strings index 81b5db9a..ddefe0e0 100644 --- a/Example/Authenticator/Supporting Files/Localization/en.lproj/Authenticator.strings +++ b/Example/Authenticator/Supporting Files/Localization/en.lproj/Authenticator.strings @@ -69,6 +69,7 @@ "errors.no_suitable_connection" = "You have no suitable connection for this action"; "errors.no_internet_connection" = "No Internet connection."; "errors.no_internet_connection_try_again" = "Please try again later."; +"errors.no_internet_connection_check_and_try_again" = "Please check your internet connection and try again"; "errors.passcode_dont_match" = "Passcodes don't match"; "errors.wrong_passcode" = "Wrong passcode"; "errors.passcode_ios_singular" = "You entered the wrong Passcode. Please try again in %{count} minute."; diff --git a/Example/Authenticator/Utils/Helpers/Localizations.swift b/Example/Authenticator/Utils/Helpers/Localizations.swift index a01acdff..b20c076d 100644 --- a/Example/Authenticator/Utils/Helpers/Localizations.swift +++ b/Example/Authenticator/Utils/Helpers/Localizations.swift @@ -205,6 +205,7 @@ enum Localizations: String, Localizable { case noInternetConnection = "errors.no_internet_connection" case errorOccuredPleaseTryAgain = "errors.authorization_error" case pleaseTryAgain = "errors.no_internet_connection_try_again" + case pleaseCheckAndTryAgain = "errors.no_internet_connection_check_and_try_again" case inactivityMessage = "warnings.inactivity_block_message" case passcodeDontMatch = "errors.passcode_dont_match" case wrongPasscode = "errors.wrong_passcode" diff --git a/Example/Authenticator/View Controllers/Connection/ConnectViewController.swift b/Example/Authenticator/View Controllers/Connection/ConnectViewController.swift index 06675af7..a46033f4 100644 --- a/Example/Authenticator/View Controllers/Connection/ConnectViewController.swift +++ b/Example/Authenticator/View Controllers/Connection/ConnectViewController.swift @@ -45,7 +45,6 @@ final class ConnectViewController: BaseViewController { return } } - } // MARK: - Layout diff --git a/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift b/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift index 17bf8c04..9c892134 100644 --- a/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift +++ b/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift @@ -225,6 +225,7 @@ extension ConnectionsViewController: ConnectionCellEventsDelegate { } func deletePressed(id: String, showConfirmation: Bool) { + checkInternetConnection(id: id) if showConfirmation { showConfirmationAlert( withTitle: l10n(.deleteConnection), @@ -248,4 +249,18 @@ extension ConnectionsViewController: ConnectionCellEventsDelegate { func consentsPressed(id: String) { viewModel.consentsPressed(connectionId: id) } + + private func checkInternetConnection(id: String) { + guard ReachabilityManager.shared.isReachable else { + self.showConfirmationAlert( + withTitle: l10n(.noInternetConnection), + message: l10n(.pleaseCheckAndTryAgain), + confirmActionTitle: l10n(.retry), + confirmAction: { _ in + self.viewModel.remove(by: id) + } + ) + return + } + } } From 202a1fd5dc5010fc4b75f576afee433e25d11820 Mon Sep 17 00:00:00 2001 From: mnewlive Date: Tue, 3 Aug 2021 12:48:38 +0300 Subject: [PATCH 056/110] Optimize code --- .../ConnectionsViewController.swift | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift b/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift index 9c892134..ed944b78 100644 --- a/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift +++ b/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift @@ -225,21 +225,7 @@ extension ConnectionsViewController: ConnectionCellEventsDelegate { } func deletePressed(id: String, showConfirmation: Bool) { - checkInternetConnection(id: id) - if showConfirmation { - showConfirmationAlert( - withTitle: l10n(.deleteConnection), - message: l10n(.deleteConnectionDescription), - confirmActionTitle: l10n(.delete), - confirmActionStyle: .destructive, - cancelTitle: l10n(.cancel), - confirmAction: { _ in - self.viewModel.remove(by: id) - } - ) - } else { - viewModel.remove(by: id) - } + checkInternetConnection(id: id, showConfirmation: showConfirmation) } func reconnectPreseed(id: String) { @@ -250,17 +236,34 @@ extension ConnectionsViewController: ConnectionCellEventsDelegate { viewModel.consentsPressed(connectionId: id) } - private func checkInternetConnection(id: String) { + private func checkInternetConnection(id: String, showConfirmation: Bool) { guard ReachabilityManager.shared.isReachable else { self.showConfirmationAlert( withTitle: l10n(.noInternetConnection), message: l10n(.pleaseCheckAndTryAgain), confirmActionTitle: l10n(.retry), confirmAction: { _ in - self.viewModel.remove(by: id) + if (ReachabilityManager.shared.isReachable) { + self.viewModel.remove(by: id) + } } ) return } + + if showConfirmation { + showConfirmationAlert( + withTitle: l10n(.deleteConnection), + message: l10n(.deleteConnectionDescription), + confirmActionTitle: l10n(.delete), + confirmActionStyle: .destructive, + cancelTitle: l10n(.cancel), + confirmAction: { _ in + self.viewModel.remove(by: id) + } + ) + } else { + viewModel.remove(by: id) + } } } From d473d7364846edd6c5016f512efb1705e01f9d20 Mon Sep 17 00:00:00 2001 From: mnewlive Date: Tue, 3 Aug 2021 16:21:46 +0300 Subject: [PATCH 057/110] Minor changes --- .../Connection/ConnectionsViewController.swift | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift b/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift index ed944b78..6995296d 100644 --- a/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift +++ b/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift @@ -225,7 +225,7 @@ extension ConnectionsViewController: ConnectionCellEventsDelegate { } func deletePressed(id: String, showConfirmation: Bool) { - checkInternetConnection(id: id, showConfirmation: showConfirmation) + checkInternetAndRemoveConnection(id: id, showConfirmation: showConfirmation) } func reconnectPreseed(id: String) { @@ -236,16 +236,14 @@ extension ConnectionsViewController: ConnectionCellEventsDelegate { viewModel.consentsPressed(connectionId: id) } - private func checkInternetConnection(id: String, showConfirmation: Bool) { + private func checkInternetAndRemoveConnection(id: String, showConfirmation: Bool) { guard ReachabilityManager.shared.isReachable else { self.showConfirmationAlert( withTitle: l10n(.noInternetConnection), message: l10n(.pleaseCheckAndTryAgain), confirmActionTitle: l10n(.retry), confirmAction: { _ in - if (ReachabilityManager.shared.isReachable) { - self.viewModel.remove(by: id) - } + if ReachabilityManager.shared.isReachable { self.viewModel.remove(by: id) } } ) return From f11207f26a45ed7ed89bad526eae8d8b7d7f64d2 Mon Sep 17 00:00:00 2001 From: mnewlive Date: Tue, 3 Aug 2021 17:10:27 +0300 Subject: [PATCH 058/110] Added realm migrations for Connection --- .../Authenticator.xcodeproj/project.pbxproj | 6 +++ .../Migrations/AddConnectionV2Fields.swift | 39 +++++++++++++++++++ .../Database/RealmMigrationManager.swift | 3 +- .../Models/Database/Connection.swift | 2 - 4 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 Example/Authenticator/Database/Migrations/AddConnectionV2Fields.swift diff --git a/Example/Authenticator.xcodeproj/project.pbxproj b/Example/Authenticator.xcodeproj/project.pbxproj index cf8ff581..a333ffaa 100644 --- a/Example/Authenticator.xcodeproj/project.pbxproj +++ b/Example/Authenticator.xcodeproj/project.pbxproj @@ -9,6 +9,8 @@ /* Begin PBXBuildFile section */ 0479C5522387F8C300F3FE0A /* AVCaptureHelperSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0479C5512387F8C300F3FE0A /* AVCaptureHelperSpec.swift */; }; 6CF5A0163F3E926E5B271FBE /* Pods_Authenticator_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC7966F04DE3EFEB4C47030C /* Pods_Authenticator_Tests.framework */; }; + 84726A0726B98538005C792E /* AddConnectionV2Fields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84726A0626B98538005C792E /* AddConnectionV2Fields.swift */; }; + 84726A0826B98538005C792E /* AddConnectionV2Fields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84726A0626B98538005C792E /* AddConnectionV2Fields.swift */; }; 95033CC125F8FB0D00D64BDE /* MockLocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95033CC025F8FB0D00D64BDE /* MockLocationManager.swift */; }; 954CC97524A22FF30021F0B2 /* SEEncryptedDataExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED81322CE18270050ED3C /* SEEncryptedDataExtensions.swift */; }; 954CC97624A22FF40021F0B2 /* SEEncryptedDataExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED81322CE18270050ED3C /* SEEncryptedDataExtensions.swift */; }; @@ -393,6 +395,7 @@ 607FACD01AFB9204008FA782 /* Authenticator_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Authenticator_Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 607FACE51AFB9204008FA782 /* Authenticator_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Authenticator_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 84726A0626B98538005C792E /* AddConnectionV2Fields.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddConnectionV2Fields.swift; sourceTree = ""; }; 95033CC025F8FB0D00D64BDE /* MockLocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockLocationManager.swift; sourceTree = ""; }; 957BCE5E24A3921B001F456F /* Log.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = ""; }; 9581A9DC2477E0C00066EF5E /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = ""; }; @@ -760,6 +763,7 @@ children = ( 95D784E523042BCE002BF219 /* AddConnectionSupportEmail.swift */, 95C8C07925F251E6005E787F /* AddConnectionGeolocation.swift */, + 84726A0626B98538005C792E /* AddConnectionV2Fields.swift */, ); path = Migrations; sourceTree = ""; @@ -1913,6 +1917,7 @@ 9DE56A0C24AB702A00FA5B2C /* ConsentsCoordinator.swift in Sources */, 9DCED90322CE18280050ED3C /* TaptileFeedbackButton.swift in Sources */, 9DCED8E022CE18280050ED3C /* UserDefaultsHelper.swift in Sources */, + 84726A0726B98538005C792E /* AddConnectionV2Fields.swift in Sources */, 9DCC5E25265D1DD40054B933 /* ConnectionsInteractorV2.swift in Sources */, 9DD155F326613CEA00AEFA0D /* BaseConnectionsInteractor.swift in Sources */, 9DCED8D522CE18280050ED3C /* ApplicationExtensions.swift in Sources */, @@ -2137,6 +2142,7 @@ 9DE3249722D399E800EB162A /* ViewsExtensions.swift in Sources */, 9DE3249522D399E300EB162A /* ViewControllerExtensions.swift in Sources */, 95D784E82304399B002BF219 /* AddConnectionSupportEmail.swift in Sources */, + 84726A0826B98538005C792E /* AddConnectionV2Fields.swift in Sources */, 959E30B4247BE56C0067354A /* SettingCellModel.swift in Sources */, 9D66E1AB22D4C1CA00BD59B6 /* CryptoErrorsSpec.swift in Sources */, 9D630BCA243DC464000A9ACC /* OnboardingViewModel.swift in Sources */, diff --git a/Example/Authenticator/Database/Migrations/AddConnectionV2Fields.swift b/Example/Authenticator/Database/Migrations/AddConnectionV2Fields.swift new file mode 100644 index 00000000..281b8ae0 --- /dev/null +++ b/Example/Authenticator/Database/Migrations/AddConnectionV2Fields.swift @@ -0,0 +1,39 @@ +// +// AddConnectionV2Fields +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import RealmSwift + +struct AddConnectionV2Fields: RealmMigratable { + static func execute(_ migration: Migration) { + migration.enumerateObjects(ofType: Connection.className()) { (_, newObject) in + guard let object = newObject else { return } + + let apiVersionPath = #keyPath(Connection.apiVersion) + object[apiVersionPath] = "" + let providerIdPath = #keyPath(Connection.providerId) + object[providerIdPath] = "" + let publicKeyPath = #keyPath(Connection.publicKey) + object[publicKeyPath] = "" + } + } +} diff --git a/Example/Authenticator/Database/RealmMigrationManager.swift b/Example/Authenticator/Database/RealmMigrationManager.swift index 7bc7760c..af363dbf 100644 --- a/Example/Authenticator/Database/RealmMigrationManager.swift +++ b/Example/Authenticator/Database/RealmMigrationManager.swift @@ -29,7 +29,8 @@ protocol RealmMigratable { private let availableMigrations: [RealmMigratable.Type] = [ AddConnectionSupportEmail.self, - AddConnectionGeolocation.self + AddConnectionGeolocation.self, + AddConnectionV2Fields.self ] struct RealmMigrationManager { diff --git a/Example/Authenticator/Models/Database/Connection.swift b/Example/Authenticator/Models/Database/Connection.swift index 4b51d3b8..474840d6 100644 --- a/Example/Authenticator/Models/Database/Connection.swift +++ b/Example/Authenticator/Models/Database/Connection.swift @@ -28,7 +28,6 @@ enum ConnectionStatus: String { case inactive } -// TODO: Add migrations @objcMembers final class Connection: Object { dynamic var id: String = "" dynamic var guid: String = UUID().uuidString @@ -42,7 +41,6 @@ enum ConnectionStatus: String { dynamic let geolocationRequired = RealmOptional() dynamic var createdAt: Date = Date() dynamic var updatedAt: Date = Date() - dynamic var providerId: String? dynamic var publicKey: String = "" dynamic var apiVersion: String = "1" From 5c48a998974ec53e1ce6684a4ce6d73c9b140f25 Mon Sep 17 00:00:00 2001 From: mnewlive Date: Tue, 3 Aug 2021 17:46:15 +0300 Subject: [PATCH 059/110] Solved the problem with running tests on M1 --- Example/Authenticator.xcodeproj/project.pbxproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Example/Authenticator.xcodeproj/project.pbxproj b/Example/Authenticator.xcodeproj/project.pbxproj index a333ffaa..66f054c2 100644 --- a/Example/Authenticator.xcodeproj/project.pbxproj +++ b/Example/Authenticator.xcodeproj/project.pbxproj @@ -2394,6 +2394,7 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; DEVELOPMENT_TEAM = 8QFQG87EDC; + "EXCLUDED_ARCHS[sdk=*]" = arm64; FRAMEWORK_SEARCH_PATHS = ( "$(PLATFORM_DIR)/Developer/Library/Frameworks", "$(inherited)", @@ -2419,6 +2420,7 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; DEVELOPMENT_TEAM = 8QFQG87EDC; + "EXCLUDED_ARCHS[sdk=*]" = arm64; FRAMEWORK_SEARCH_PATHS = ( "$(PLATFORM_DIR)/Developer/Library/Frameworks", "$(inherited)", @@ -2450,6 +2452,7 @@ CODE_SIGN_STYLE = Automatic; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 8QFQG87EDC; + "EXCLUDED_ARCHS[sdk=*]" = arm64; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "TestHost-iOS/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; @@ -2479,6 +2482,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = 8QFQG87EDC; + "EXCLUDED_ARCHS[sdk=*]" = arm64; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "TestHost-iOS/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; From 5969ae0e1249df95f1f262ff7703bfcadd709a75 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Wed, 4 Aug 2021 14:48:26 +0300 Subject: [PATCH 060/110] fixed consent days left till expiration --- .../Connections/ConsentDetailViewModel.swift | 2 +- .../View Models/Connections/ConsentViewModel.swift | 2 +- Example/Tests/Extensions/DateExtensionsSpec.swift | 8 ++++++++ .../Classes/DateExtensions.swift | 11 +++++++++++ 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/Example/Authenticator/View Models/Connections/ConsentDetailViewModel.swift b/Example/Authenticator/View Models/Connections/ConsentDetailViewModel.swift index be54eb9a..9ef9eb67 100644 --- a/Example/Authenticator/View Models/Connections/ConsentDetailViewModel.swift +++ b/Example/Authenticator/View Models/Connections/ConsentDetailViewModel.swift @@ -45,7 +45,7 @@ final class ConsentDetailViewModel { } var expiresInText: String { - return "\(consent.expiresAt.get(.day)) \(l10n(.daysLeft))" + return "\(consent.expiresAt.numberOfDaysFromNow) \(l10n(.daysLeft))" } var descriptionAtributedString: NSMutableAttributedString { diff --git a/Example/Authenticator/View Models/Connections/ConsentViewModel.swift b/Example/Authenticator/View Models/Connections/ConsentViewModel.swift index 177e3266..d8fb8106 100644 --- a/Example/Authenticator/View Models/Connections/ConsentViewModel.swift +++ b/Example/Authenticator/View Models/Connections/ConsentViewModel.swift @@ -73,7 +73,7 @@ final class ConsentsViewModel { let expirationAttributedString = NSMutableAttributedString() - let numberOfDaysToExpire = consent.expiresAt.get(.day) + let numberOfDaysToExpire = consent.expiresAt.numberOfDaysFromNow let expiresInString = numberOfDaysToExpire == 1 ? "\(numberOfDaysToExpire) \(l10n(.day))" : "\(numberOfDaysToExpire) \(l10n(.days))" let expiresInAttributedMessage = NSMutableAttributedString( diff --git a/Example/Tests/Extensions/DateExtensionsSpec.swift b/Example/Tests/Extensions/DateExtensionsSpec.swift index 4fc8640d..7c5d8b9a 100644 --- a/Example/Tests/Extensions/DateExtensionsSpec.swift +++ b/Example/Tests/Extensions/DateExtensionsSpec.swift @@ -38,5 +38,13 @@ class DateExtensionsSpec: BaseSpec { expect(date.utcSeconds).to(equal(1568285040)) } } + + describe("numberOfDaysFromNow") { + it("should return difference betwen dates from now") { + let twoDaysFromNow = Calendar.current.date(byAdding: .day, value: 2, to: Date())! + + expect(twoDaysFromNow.numberOfDaysFromNow).to(equal(2)) + } + } } } diff --git a/SaltedgeAuthenticatorCore/Classes/DateExtensions.swift b/SaltedgeAuthenticatorCore/Classes/DateExtensions.swift index 19ae7ec7..003e4c94 100644 --- a/SaltedgeAuthenticatorCore/Classes/DateExtensions.swift +++ b/SaltedgeAuthenticatorCore/Classes/DateExtensions.swift @@ -40,6 +40,17 @@ public extension Date { return currentCalendar.date(from: components)! } + var numberOfDaysFromNow: Int { + let calendar = Calendar.current + + let date1 = calendar.startOfDay(for: Date()) + let date2 = calendar.startOfDay(for: self) + + let components = calendar.dateComponents([.day], from: date1, to: date2) + + return components.day ?? 0 + } + func get(_ component: Calendar.Component, calendar: Calendar = Calendar.current) -> Int { return calendar.component(component, from: self) } From c097ea7f49a948ba160af0d7a0918daeee82fb1f Mon Sep 17 00:00:00 2001 From: mnewlive Date: Thu, 5 Aug 2021 19:40:14 +0300 Subject: [PATCH 061/110] Modified logic in ConnectionViewModel also added some tests --- .../Authenticator.xcodeproj/project.pbxproj | 4 + .../Connections/ConnectionsCoordinator.swift | 24 ++ .../ConnectionsViewController.swift | 32 +-- .../Connections/ConnectionsViewModel.swift | 15 +- .../ViewModels/ConnectionsViewModel.swift | 23 ++ .../ViewModels/ConnectionsViewModelSpec.swift | 216 ++++++++++++++++++ 6 files changed, 283 insertions(+), 31 deletions(-) create mode 100644 Example/Tests/ViewModels/ConnectionsViewModel.swift create mode 100644 Example/Tests/ViewModels/ConnectionsViewModelSpec.swift diff --git a/Example/Authenticator.xcodeproj/project.pbxproj b/Example/Authenticator.xcodeproj/project.pbxproj index 66f054c2..f6b062a5 100644 --- a/Example/Authenticator.xcodeproj/project.pbxproj +++ b/Example/Authenticator.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 6CF5A0163F3E926E5B271FBE /* Pods_Authenticator_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC7966F04DE3EFEB4C47030C /* Pods_Authenticator_Tests.framework */; }; 84726A0726B98538005C792E /* AddConnectionV2Fields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84726A0626B98538005C792E /* AddConnectionV2Fields.swift */; }; 84726A0826B98538005C792E /* AddConnectionV2Fields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84726A0626B98538005C792E /* AddConnectionV2Fields.swift */; }; + 8480F53B26BC182200FB5DFF /* ConnectionsViewModelSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8480F53A26BC182200FB5DFF /* ConnectionsViewModelSpec.swift */; }; 95033CC125F8FB0D00D64BDE /* MockLocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95033CC025F8FB0D00D64BDE /* MockLocationManager.swift */; }; 954CC97524A22FF30021F0B2 /* SEEncryptedDataExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED81322CE18270050ED3C /* SEEncryptedDataExtensions.swift */; }; 954CC97624A22FF40021F0B2 /* SEEncryptedDataExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED81322CE18270050ED3C /* SEEncryptedDataExtensions.swift */; }; @@ -396,6 +397,7 @@ 607FACE51AFB9204008FA782 /* Authenticator_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Authenticator_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 84726A0626B98538005C792E /* AddConnectionV2Fields.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddConnectionV2Fields.swift; sourceTree = ""; }; + 8480F53A26BC182200FB5DFF /* ConnectionsViewModelSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionsViewModelSpec.swift; sourceTree = ""; }; 95033CC025F8FB0D00D64BDE /* MockLocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockLocationManager.swift; sourceTree = ""; }; 957BCE5E24A3921B001F456F /* Log.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = ""; }; 9581A9DC2477E0C00066EF5E /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = ""; }; @@ -968,6 +970,7 @@ 9D8E5BA12445A1E70081E60E /* OnboardingViewModelSpec.swift */, 9DD50990247527D800CB188D /* AuthorizationsViewModelSpec.swift */, 9DE5807C2450892E004AF19F /* PasscodeViewModelSpec.swift */, + 8480F53A26BC182200FB5DFF /* ConnectionsViewModelSpec.swift */, ); path = ViewModels; sourceTree = ""; @@ -2067,6 +2070,7 @@ 9D69071C2655439200A9CE94 /* JWSHelperSpec.swift in Sources */, 9DE3249922D399ED00EB162A /* StringExtensions.swift in Sources */, 9D66E1DE22D6146900BD59B6 /* ConnectionRouterSpec.swift in Sources */, + 8480F53B26BC182200FB5DFF /* ConnectionsViewModelSpec.swift in Sources */, 9DE324CE22D39A9800EB162A /* CustomSpacingLabel.swift in Sources */, 9DE569FF24AA171400FA5B2C /* ConsentsViewController.swift in Sources */, 9D5AC68B23571958003B00E2 /* LicensesViewController.swift in Sources */, diff --git a/Example/Authenticator/Coordinators/Connections/ConnectionsCoordinator.swift b/Example/Authenticator/Coordinators/Connections/ConnectionsCoordinator.swift index 08c85a6c..e56ee4be 100644 --- a/Example/Authenticator/Coordinators/Connections/ConnectionsCoordinator.swift +++ b/Example/Authenticator/Coordinators/Connections/ConnectionsCoordinator.swift @@ -48,6 +48,30 @@ final class ConnectionsCoordinator: Coordinator { // MARK: - ConnectionsListEventsDelegate extension ConnectionsCoordinator: ConnectionsEventsDelegate { + func showNoInternetConnectionAlert(completion:@escaping () -> Void) { + self.currentViewController.showConfirmationAlert( + withTitle: l10n(.noInternetConnection), + message: l10n(.pleaseCheckAndTryAgain), + confirmActionTitle: l10n(.retry), + confirmAction: { _ in + completion() + } + ) + } + + func showDeleteConfirmationAlert(completion:@escaping () -> Void) { + self.currentViewController.showConfirmationAlert( + withTitle: l10n(.deleteConnection), + message: l10n(.deleteConnectionDescription), + confirmActionTitle: l10n(.delete), + confirmActionStyle: .destructive, + cancelTitle: l10n(.cancel), + confirmAction: { _ in + completion() + } + ) + } + func addPressed() { guard AVCaptureHelper.cameraIsAuthorized() else { self.currentViewController.showConfirmationAlert( diff --git a/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift b/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift index 6995296d..e04abd9d 100644 --- a/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift +++ b/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift @@ -225,7 +225,8 @@ extension ConnectionsViewController: ConnectionCellEventsDelegate { } func deletePressed(id: String, showConfirmation: Bool) { - checkInternetAndRemoveConnection(id: id, showConfirmation: showConfirmation) +// checkInternetAndRemoveConnection(id: id, showConfirmation: showConfirmation) + viewModel.checkInternetAndRemoveConnection(id: id, showConfirmation: showConfirmation) } func reconnectPreseed(id: String) { @@ -235,33 +236,4 @@ extension ConnectionsViewController: ConnectionCellEventsDelegate { func consentsPressed(id: String) { viewModel.consentsPressed(connectionId: id) } - - private func checkInternetAndRemoveConnection(id: String, showConfirmation: Bool) { - guard ReachabilityManager.shared.isReachable else { - self.showConfirmationAlert( - withTitle: l10n(.noInternetConnection), - message: l10n(.pleaseCheckAndTryAgain), - confirmActionTitle: l10n(.retry), - confirmAction: { _ in - if ReachabilityManager.shared.isReachable { self.viewModel.remove(by: id) } - } - ) - return - } - - if showConfirmation { - showConfirmationAlert( - withTitle: l10n(.deleteConnection), - message: l10n(.deleteConnectionDescription), - confirmActionTitle: l10n(.delete), - confirmActionStyle: .destructive, - cancelTitle: l10n(.cancel), - confirmAction: { _ in - self.viewModel.remove(by: id) - } - ) - } else { - viewModel.remove(by: id) - } - } } diff --git a/Example/Authenticator/View Models/Connections/ConnectionsViewModel.swift b/Example/Authenticator/View Models/Connections/ConnectionsViewModel.swift index 8e2fbb13..1ee54340 100644 --- a/Example/Authenticator/View Models/Connections/ConnectionsViewModel.swift +++ b/Example/Authenticator/View Models/Connections/ConnectionsViewModel.swift @@ -34,6 +34,8 @@ protocol ConnectionsEventsDelegate: class { func updateViews() func addPressed() func presentError(_ error: String) + func showNoInternetConnectionAlert(completion:@escaping () -> Void) + func showDeleteConfirmationAlert(completion:@escaping () -> Void) } final class ConnectionsViewModel { @@ -64,7 +66,7 @@ final class ConnectionsViewModel { var emptyViewData: EmptyViewData { return EmptyViewData( - image: #imageLiteral(resourceName: "noConnections"), + image: UIImage(named: "noConnections", in: .authenticator_main, compatibleWith: nil)!, title: l10n(.noConnections), description: l10n(.noConnectionsDescription), buttonTitle: l10n(.connect) @@ -170,6 +172,17 @@ extension ConnectionsViewModel { func reconnect(id: ID) { delegate?.reconnect(by: id) } + + func checkInternetAndRemoveConnection(id: String, showConfirmation: Bool) { + guard ReachabilityManager.shared.isReachable else { + delegate?.showNoInternetConnectionAlert { + if ReachabilityManager.shared.isReachable { self.remove(by: id) } + } + return + } + if showConfirmation { delegate?.showDeleteConfirmationAlert { self.remove(by: id) } } + else { remove(by: id) } + } } // MARK: - Actions diff --git a/Example/Tests/ViewModels/ConnectionsViewModel.swift b/Example/Tests/ViewModels/ConnectionsViewModel.swift new file mode 100644 index 00000000..be7671d9 --- /dev/null +++ b/Example/Tests/ViewModels/ConnectionsViewModel.swift @@ -0,0 +1,23 @@ +// +// ConnectionsViewModel +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation diff --git a/Example/Tests/ViewModels/ConnectionsViewModelSpec.swift b/Example/Tests/ViewModels/ConnectionsViewModelSpec.swift new file mode 100644 index 00000000..567f299c --- /dev/null +++ b/Example/Tests/ViewModels/ConnectionsViewModelSpec.swift @@ -0,0 +1,216 @@ +// +// ConnectionsViewModelSpec +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import Quick +import Nimble +import UIKit +import SEAuthenticator +import RealmSwift +@testable import SEAuthenticatorCore + +private final class MockSettingsDelegate: ConnectionsEventsDelegate { + + var showEditConnectionAlertCall: Bool = false + var showSupportCall: Bool = false + var deleteConnectionCall: Bool = false + var reconnectCall: Bool = false + var consentsPressedCall: Bool = false + var updateViewsCall: Bool = false + var addPressedCall: Bool = false + var presentErrorCall: Bool = false + var showNoInternetConnectionAlertCall: Bool = false + var showDeleteConfirmationAlertCall: Bool = false + + func showEditConnectionAlert(placeholder: String, completion: @escaping (String) -> ()) { + showEditConnectionAlertCall = true + } + + func showSupport(email: String) { + showSupportCall = true + } + + func deleteConnection(completion: @escaping () -> ()) { + deleteConnectionCall = true + } + + func reconnect(by id: String) { + reconnectCall = true + } + + func consentsPressed(connectionId: String, consents: [SEConsentData]) { + consentsPressedCall = true + } + + func updateViews() { + updateViewsCall = true + } + + func addPressed() { + addPressedCall = true + } + + func presentError(_ error: String) { + presentErrorCall = true + } + + func showNoInternetConnectionAlert(completion: @escaping () -> Void) { + showNoInternetConnectionAlertCall = true + } + + func showDeleteConfirmationAlert(completion: @escaping () -> Void) { + showDeleteConfirmationAlertCall = true + } +} + + +final class ConnectionsViewModelSpec: BaseSpec { + + override func spec() { + beforeEach { + ConnectionRepository.deleteAllConnections() + } + + describe("count") { + it("should return count of connections") { + let viewModel = ConnectionsViewModel() + let connection = Connection() + connection.status = "active" + + ConnectionRepository.save(connection) + + expect(viewModel.count).to(equal(1)) + } + } + + describe("hasDataToShow") { + it("must display the data that needs to be shown") { + let viewModel = ConnectionsViewModel() + let connection = Connection() + connection.status = "active" + + ConnectionRepository.save(connection) + + expect(viewModel.hasDataToShow).to(equal(true)) + } + + it("will not display the data to be displayed") { + let viewModel = ConnectionsViewModel() + + expect(viewModel.hasDataToShow).to(equal(false)) + } + } + + describe("emptyViewData") { + context("when there is no connections") { + it("should return correct data") { + let viewModel = ConnectionsViewModel() + + let expectedEmptyViewData = EmptyViewData( + image: UIImage(named: "noConnections", in: .authenticator_main, compatibleWith: nil)!, + title: l10n(.noConnections), + description: l10n(.noConnectionsDescription), + buttonTitle: l10n(.connect) + ) + + expect(viewModel.emptyViewData).to(equal(expectedEmptyViewData)) + } + } + } + + describe("remove(id)") { + context("remove connections") { + it("should remove connection") { + let viewModel = ConnectionsViewModel() + let mockDelegate = MockSettingsDelegate() + viewModel.delegate = mockDelegate + + let connection = Connection() + connection.id = "id1" + + ConnectionRepository.save(connection) + + viewModel.remove(by: "id1") + + expect(viewModel.count).to(equal(0)) + } + } + } + + describe("checkInternetAndRemoveConnection") { + context("remove connection when internet is on and showConfirmation is true") { + it("should remove connection") { + let viewModel = ConnectionsViewModel() + let mockDelegate = MockSettingsDelegate() + viewModel.delegate = mockDelegate + + let connection = Connection() + connection.id = "id1" + + ConnectionRepository.save(connection) + + viewModel.checkInternetAndRemoveConnection(id: "id1", showConfirmation: true) + + expect(mockDelegate.showNoInternetConnectionAlertCall).to(beTrue()) + expect(viewModel.count).to(equal(0)) + } + } + + context("remove connection when internet is on and showConfirmation is false") { + it("should remove connection") { + let viewModel = ConnectionsViewModel() + let mockDelegate = MockSettingsDelegate() + viewModel.delegate = mockDelegate + + let connection = Connection() + connection.id = "id1" + + ConnectionRepository.save(connection) + + viewModel.checkInternetAndRemoveConnection(id: "id1", showConfirmation: false) + + expect(mockDelegate.showNoInternetConnectionAlertCall).to(beTrue()) + expect(viewModel.count).to(equal(0)) + } + } + + context("remove connection when internet is off") { + it("should remove connection") { + let viewModel = ConnectionsViewModel() + let mockDelegate = MockSettingsDelegate() + viewModel.delegate = mockDelegate + + let connection = Connection() + connection.id = "id1" + + ConnectionRepository.save(connection) + + viewModel.checkInternetAndRemoveConnection(id: "id1", showConfirmation: true) + + expect(mockDelegate.showDeleteConfirmationAlertCall).to(beTrue()) + expect(viewModel.count).to(equal(0)) + } + } + } + } +} + From 24b74d8d17462f920e997b8d4e2bdee3b3445197 Mon Sep 17 00:00:00 2001 From: mnewlive Date: Thu, 5 Aug 2021 21:25:12 +0300 Subject: [PATCH 062/110] Added todo, fixed tests, implement new protocol --- .../Connections/ConnectionsCoordinator.swift | 2 +- .../Utils/Helpers/ReachabilityManager.swift | 6 +- .../Connection/ConnectViewController.swift | 24 +++---- .../Connections/ConnectionsViewModel.swift | 9 ++- .../ViewModels/ConnectionsViewModelSpec.swift | 71 ++++++++++--------- 5 files changed, 63 insertions(+), 49 deletions(-) diff --git a/Example/Authenticator/Coordinators/Connections/ConnectionsCoordinator.swift b/Example/Authenticator/Coordinators/Connections/ConnectionsCoordinator.swift index e56ee4be..c312090f 100644 --- a/Example/Authenticator/Coordinators/Connections/ConnectionsCoordinator.swift +++ b/Example/Authenticator/Coordinators/Connections/ConnectionsCoordinator.swift @@ -29,7 +29,7 @@ final class ConnectionsCoordinator: Coordinator { private var connectViewCoordinator: ConnectViewCoordinator? private var qrCodeCoordinator: QRCodeCoordinator? private var consentsCoordinator: ConsentsCoordinator? - private var viewModel = ConnectionsViewModel() + private var viewModel = ConnectionsViewModel(reachabilityManager: ReachabilityManager.shared) init(rootViewController: UIViewController) { self.rootViewController = rootViewController diff --git a/Example/Authenticator/Utils/Helpers/ReachabilityManager.swift b/Example/Authenticator/Utils/Helpers/ReachabilityManager.swift index 26467e43..88ab6278 100644 --- a/Example/Authenticator/Utils/Helpers/ReachabilityManager.swift +++ b/Example/Authenticator/Utils/Helpers/ReachabilityManager.swift @@ -23,7 +23,7 @@ import Foundation import Reachability -class ReachabilityManager { +class ReachabilityManager: ReachabilityManagerProtocol { static let shared = ReachabilityManager() private var reachability: Reachability! @@ -68,3 +68,7 @@ class ReachabilityManager { NotificationsHelper.removeObserver(self) } } + +protocol ReachabilityManagerProtocol { + var isReachable: Bool { get } +} diff --git a/Example/Authenticator/View Controllers/Connection/ConnectViewController.swift b/Example/Authenticator/View Controllers/Connection/ConnectViewController.swift index a46033f4..20eadc2d 100644 --- a/Example/Authenticator/View Controllers/Connection/ConnectViewController.swift +++ b/Example/Authenticator/View Controllers/Connection/ConnectViewController.swift @@ -32,18 +32,18 @@ final class ConnectViewController: BaseViewController { layout() } - private func checkInternetConnection() { - guard ReachabilityManager.shared.isReachable else { - self.showInfoAlert( - withTitle: l10n(.noInternetConnection), - message: l10n(.pleaseTryAgain), - actionTitle: l10n(.ok), - completion: { - self.cancelPressed() - } - ) - return - } + private func checkInternetConnection() { //TODO: Move logic to ViewModel, in controller we have logic only with UI +// guard ReachabilityManager.shared.isReachable else { +// self.showInfoAlert( +// withTitle: l10n(.noInternetConnection), +// message: l10n(.pleaseTryAgain), +// actionTitle: l10n(.ok), +// completion: { +// self.cancelPressed() +// } +// ) +// return +// } } } diff --git a/Example/Authenticator/View Models/Connections/ConnectionsViewModel.swift b/Example/Authenticator/View Models/Connections/ConnectionsViewModel.swift index 1ee54340..e346ac49 100644 --- a/Example/Authenticator/View Models/Connections/ConnectionsViewModel.swift +++ b/Example/Authenticator/View Models/Connections/ConnectionsViewModel.swift @@ -47,7 +47,10 @@ final class ConnectionsViewModel { private var connectionsNotificationToken: NotificationToken? private var connectionsListener: RealmConnectionsListener? - init() { + private let reachabilityManager: ReachabilityManagerProtocol + + init(reachabilityManager: ReachabilityManagerProtocol) { + self.reachabilityManager = reachabilityManager connectionsListener = RealmConnectionsListener( onDataChange: { self.delegate?.updateViews() @@ -174,9 +177,9 @@ extension ConnectionsViewModel { } func checkInternetAndRemoveConnection(id: String, showConfirmation: Bool) { - guard ReachabilityManager.shared.isReachable else { + guard reachabilityManager.isReachable else { delegate?.showNoInternetConnectionAlert { - if ReachabilityManager.shared.isReachable { self.remove(by: id) } + if self.reachabilityManager.isReachable { self.remove(by: id) } } return } diff --git a/Example/Tests/ViewModels/ConnectionsViewModelSpec.swift b/Example/Tests/ViewModels/ConnectionsViewModelSpec.swift index 567f299c..1844034e 100644 --- a/Example/Tests/ViewModels/ConnectionsViewModelSpec.swift +++ b/Example/Tests/ViewModels/ConnectionsViewModelSpec.swift @@ -28,6 +28,13 @@ import SEAuthenticator import RealmSwift @testable import SEAuthenticatorCore +class ReachabilityManagerSpy: ReachabilityManagerProtocol { + var enabled = true + var isReachable: Bool { + return enabled + } +} + private final class MockSettingsDelegate: ConnectionsEventsDelegate { var showEditConnectionAlertCall: Bool = false @@ -82,49 +89,52 @@ private final class MockSettingsDelegate: ConnectionsEventsDelegate { } } - final class ConnectionsViewModelSpec: BaseSpec { + private var viewModel: ConnectionsViewModel! + private var networkSpy: ReachabilityManagerSpy! + + override func spec() { beforeEach { + self.networkSpy = ReachabilityManagerSpy() + self.viewModel = ConnectionsViewModel(reachabilityManager: self.networkSpy) ConnectionRepository.deleteAllConnections() } + afterEach { + self.viewModel = nil + self.networkSpy = nil + } describe("count") { it("should return count of connections") { - let viewModel = ConnectionsViewModel() let connection = Connection() connection.status = "active" ConnectionRepository.save(connection) - expect(viewModel.count).to(equal(1)) + expect(self.viewModel.count).to(equal(1)) } } describe("hasDataToShow") { it("must display the data that needs to be shown") { - let viewModel = ConnectionsViewModel() let connection = Connection() connection.status = "active" ConnectionRepository.save(connection) - expect(viewModel.hasDataToShow).to(equal(true)) + expect(self.viewModel.hasDataToShow).to(equal(true)) } it("will not display the data to be displayed") { - let viewModel = ConnectionsViewModel() - - expect(viewModel.hasDataToShow).to(equal(false)) + expect(self.viewModel.hasDataToShow).to(equal(false)) } } describe("emptyViewData") { context("when there is no connections") { it("should return correct data") { - let viewModel = ConnectionsViewModel() - let expectedEmptyViewData = EmptyViewData( image: UIImage(named: "noConnections", in: .authenticator_main, compatibleWith: nil)!, title: l10n(.noConnections), @@ -132,7 +142,7 @@ final class ConnectionsViewModelSpec: BaseSpec { buttonTitle: l10n(.connect) ) - expect(viewModel.emptyViewData).to(equal(expectedEmptyViewData)) + expect(self.viewModel.emptyViewData).to(equal(expectedEmptyViewData)) } } } @@ -140,18 +150,17 @@ final class ConnectionsViewModelSpec: BaseSpec { describe("remove(id)") { context("remove connections") { it("should remove connection") { - let viewModel = ConnectionsViewModel() let mockDelegate = MockSettingsDelegate() - viewModel.delegate = mockDelegate + self.viewModel.delegate = mockDelegate let connection = Connection() connection.id = "id1" ConnectionRepository.save(connection) - viewModel.remove(by: "id1") + self.viewModel.remove(by: "id1") - expect(viewModel.count).to(equal(0)) + expect(self.viewModel.count).to(equal(0)) } } } @@ -159,55 +168,53 @@ final class ConnectionsViewModelSpec: BaseSpec { describe("checkInternetAndRemoveConnection") { context("remove connection when internet is on and showConfirmation is true") { it("should remove connection") { - let viewModel = ConnectionsViewModel() let mockDelegate = MockSettingsDelegate() - viewModel.delegate = mockDelegate - + self.viewModel.delegate = mockDelegate + let connection = Connection() connection.id = "id1" ConnectionRepository.save(connection) + self.networkSpy.enabled = false + + self.viewModel.checkInternetAndRemoveConnection(id: "id1", showConfirmation: true) - viewModel.checkInternetAndRemoveConnection(id: "id1", showConfirmation: true) - expect(mockDelegate.showNoInternetConnectionAlertCall).to(beTrue()) - expect(viewModel.count).to(equal(0)) } } - + context("remove connection when internet is on and showConfirmation is false") { it("should remove connection") { - let viewModel = ConnectionsViewModel() let mockDelegate = MockSettingsDelegate() - viewModel.delegate = mockDelegate - + self.viewModel.delegate = mockDelegate + let connection = Connection() connection.id = "id1" ConnectionRepository.save(connection) + self.networkSpy.enabled = false + + self.viewModel.checkInternetAndRemoveConnection(id: "id1", showConfirmation: false) - viewModel.checkInternetAndRemoveConnection(id: "id1", showConfirmation: false) - expect(mockDelegate.showNoInternetConnectionAlertCall).to(beTrue()) - expect(viewModel.count).to(equal(0)) } } context("remove connection when internet is off") { it("should remove connection") { - let viewModel = ConnectionsViewModel() let mockDelegate = MockSettingsDelegate() - viewModel.delegate = mockDelegate + self.viewModel.delegate = mockDelegate let connection = Connection() connection.id = "id1" ConnectionRepository.save(connection) + + self.networkSpy.enabled = true - viewModel.checkInternetAndRemoveConnection(id: "id1", showConfirmation: true) + self.viewModel.checkInternetAndRemoveConnection(id: "id1", showConfirmation: true) expect(mockDelegate.showDeleteConfirmationAlertCall).to(beTrue()) - expect(viewModel.count).to(equal(0)) } } } From ff2917616ea0b81703c47fa5564afa47d15be35e Mon Sep 17 00:00:00 2001 From: mnewlive Date: Thu, 5 Aug 2021 21:31:37 +0300 Subject: [PATCH 063/110] RM unused files and comments --- .../ConnectionsViewController.swift | 1 - .../AuthorizationsViewModelSpec.swift | 2 +- .../ViewModels/ConnectionsViewModel.swift | 23 ------------------- .../ViewModels/ConnectionsViewModelSpec.swift | 1 - 4 files changed, 1 insertion(+), 26 deletions(-) delete mode 100644 Example/Tests/ViewModels/ConnectionsViewModel.swift diff --git a/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift b/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift index e04abd9d..fc7d95d7 100644 --- a/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift +++ b/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift @@ -225,7 +225,6 @@ extension ConnectionsViewController: ConnectionCellEventsDelegate { } func deletePressed(id: String, showConfirmation: Bool) { -// checkInternetAndRemoveConnection(id: id, showConfirmation: showConfirmation) viewModel.checkInternetAndRemoveConnection(id: id, showConfirmation: showConfirmation) } diff --git a/Example/Tests/ViewModels/AuthorizationsViewModelSpec.swift b/Example/Tests/ViewModels/AuthorizationsViewModelSpec.swift index 26a2417e..662ed2ad 100644 --- a/Example/Tests/ViewModels/AuthorizationsViewModelSpec.swift +++ b/Example/Tests/ViewModels/AuthorizationsViewModelSpec.swift @@ -41,7 +41,7 @@ final class AuthorizationsViewModelSpec: BaseSpec { describe("emptyViewData") { context("when there is no connections") { it("should return correct data") { - let viewModel = AuthorizationsViewModel() + let viewModel = ConnectionsViewModelSpecAuthorizationsViewModel() let dataSource = AuthorizationsDataSource() viewModel.dataSource = dataSource diff --git a/Example/Tests/ViewModels/ConnectionsViewModel.swift b/Example/Tests/ViewModels/ConnectionsViewModel.swift deleted file mode 100644 index be7671d9..00000000 --- a/Example/Tests/ViewModels/ConnectionsViewModel.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// ConnectionsViewModel -// This file is part of the Salt Edge Authenticator distribution -// (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2021 Salt Edge Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 3 or later. -// -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -// For the additional permissions granted for Salt Edge Authenticator -// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md -// - -import Foundation diff --git a/Example/Tests/ViewModels/ConnectionsViewModelSpec.swift b/Example/Tests/ViewModels/ConnectionsViewModelSpec.swift index 1844034e..700e4b63 100644 --- a/Example/Tests/ViewModels/ConnectionsViewModelSpec.swift +++ b/Example/Tests/ViewModels/ConnectionsViewModelSpec.swift @@ -93,7 +93,6 @@ final class ConnectionsViewModelSpec: BaseSpec { private var viewModel: ConnectionsViewModel! private var networkSpy: ReachabilityManagerSpy! - override func spec() { beforeEach { From b0c6f74b6474dacf582325d350c71fd887b4531d Mon Sep 17 00:00:00 2001 From: mnewlive Date: Fri, 6 Aug 2021 13:07:11 +0300 Subject: [PATCH 064/110] Move logic from ConnectViewConntroller to view model, fixed additional problems in code related to this --- .../Authenticator.xcodeproj/project.pbxproj | 6 +++ .../Connect/InstantActionCoordinator.swift | 3 +- .../Coordinators/ConnectViewCoordinator.swift | 3 +- .../Connection/ConnectViewController.swift | 41 ++++++++++------ .../Connections/ConnectViewModel.swift | 49 +++++++++++++++++++ .../Connections/ConnectionsViewModel.swift | 6 +-- 6 files changed, 88 insertions(+), 20 deletions(-) create mode 100644 Example/Authenticator/View Models/Connections/ConnectViewModel.swift diff --git a/Example/Authenticator.xcodeproj/project.pbxproj b/Example/Authenticator.xcodeproj/project.pbxproj index f6b062a5..b06424cd 100644 --- a/Example/Authenticator.xcodeproj/project.pbxproj +++ b/Example/Authenticator.xcodeproj/project.pbxproj @@ -9,6 +9,8 @@ /* Begin PBXBuildFile section */ 0479C5522387F8C300F3FE0A /* AVCaptureHelperSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0479C5512387F8C300F3FE0A /* AVCaptureHelperSpec.swift */; }; 6CF5A0163F3E926E5B271FBE /* Pods_Authenticator_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC7966F04DE3EFEB4C47030C /* Pods_Authenticator_Tests.framework */; }; + 8434188C26BD33180027CF1B /* ConnectViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8434188B26BD33180027CF1B /* ConnectViewModel.swift */; }; + 8434188D26BD33180027CF1B /* ConnectViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8434188B26BD33180027CF1B /* ConnectViewModel.swift */; }; 84726A0726B98538005C792E /* AddConnectionV2Fields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84726A0626B98538005C792E /* AddConnectionV2Fields.swift */; }; 84726A0826B98538005C792E /* AddConnectionV2Fields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84726A0626B98538005C792E /* AddConnectionV2Fields.swift */; }; 8480F53B26BC182200FB5DFF /* ConnectionsViewModelSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8480F53A26BC182200FB5DFF /* ConnectionsViewModelSpec.swift */; }; @@ -396,6 +398,7 @@ 607FACD01AFB9204008FA782 /* Authenticator_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Authenticator_Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 607FACE51AFB9204008FA782 /* Authenticator_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Authenticator_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 8434188B26BD33180027CF1B /* ConnectViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectViewModel.swift; sourceTree = ""; }; 84726A0626B98538005C792E /* AddConnectionV2Fields.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddConnectionV2Fields.swift; sourceTree = ""; }; 8480F53A26BC182200FB5DFF /* ConnectionsViewModelSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionsViewModelSpec.swift; sourceTree = ""; }; 95033CC025F8FB0D00D64BDE /* MockLocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockLocationManager.swift; sourceTree = ""; }; @@ -778,6 +781,7 @@ 9D5BCFD8247FC91F009A6B40 /* ConnectionPickerViewModel.swift */, 9DE56A0724AB672500FA5B2C /* ConsentViewModel.swift */, 9D98747524B37F3500EE89BB /* ConsentDetailViewModel.swift */, + 8434188B26BD33180027CF1B /* ConnectViewModel.swift */, ); path = Connections; sourceTree = ""; @@ -1845,6 +1849,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 8434188C26BD33180027CF1B /* ConnectViewModel.swift in Sources */, 9DCED8F922CE18280050ED3C /* PasscodeSymbolView.swift in Sources */, 9D98746D24B3536400EE89BB /* ConsentSharedDataView.swift in Sources */, 9DE94108246D4229000029A2 /* AuthorizationsViewModel.swift in Sources */, @@ -2169,6 +2174,7 @@ 9DE324BE22D39A5F00EB162A /* AuthorizationsViewController.swift in Sources */, 9D5BCFDD247FF595009A6B40 /* InstantActionHandler.swift in Sources */, 9DCBB28C243618B900DB3F85 /* Observable.swift in Sources */, + 8434188D26BD33180027CF1B /* ConnectViewModel.swift in Sources */, 9DE3247622D3997200EB162A /* ApplicationCoordinator.swift in Sources */, 9DB18ED722D4A35500BEF299 /* Connection.swift in Sources */, 9D66E1AD22D4C1D100BD59B6 /* ConnectionSpec.swift in Sources */, diff --git a/Example/Authenticator/Coordinators/Connect/InstantActionCoordinator.swift b/Example/Authenticator/Coordinators/Connect/InstantActionCoordinator.swift index 4791daec..6a376911 100644 --- a/Example/Authenticator/Coordinators/Connect/InstantActionCoordinator.swift +++ b/Example/Authenticator/Coordinators/Connect/InstantActionCoordinator.swift @@ -32,7 +32,8 @@ final class InstantActionCoordinator: Coordinator { init(rootViewController: UIViewController, qrUrl: URL) { self.rootViewController = rootViewController - self.connectViewController = ConnectViewController() + let viewModel = ConnectViewModel(reachabilityManager: ReachabilityManager.shared) + self.connectViewController = ConnectViewController(viewModel: viewModel) self.instantActionHandler = InstantActionHandler(qrUrl: qrUrl) if #available(iOS 13.0, *) { connectViewController.isModalInPresentation = true diff --git a/Example/Authenticator/Coordinators/ConnectViewCoordinator.swift b/Example/Authenticator/Coordinators/ConnectViewCoordinator.swift index a1c943ff..17bf5e63 100644 --- a/Example/Authenticator/Coordinators/ConnectViewCoordinator.swift +++ b/Example/Authenticator/Coordinators/ConnectViewCoordinator.swift @@ -43,7 +43,8 @@ final class ConnectViewCoordinator: Coordinator { private let rootViewController: UIViewController private lazy var webViewController = ConnectorWebViewController() - private var connectViewController = ConnectViewController() + private var viewModel = ConnectViewModel(reachabilityManager: ReachabilityManager.shared) + private lazy var connectViewController = ConnectViewController(viewModel: viewModel) private var qrCodeCoordinator: QRCodeCoordinator? private var connectHandler: ConnectHandler? diff --git a/Example/Authenticator/View Controllers/Connection/ConnectViewController.swift b/Example/Authenticator/View Controllers/Connection/ConnectViewController.swift index 20eadc2d..ef749e2e 100644 --- a/Example/Authenticator/View Controllers/Connection/ConnectViewController.swift +++ b/Example/Authenticator/View Controllers/Connection/ConnectViewController.swift @@ -24,26 +24,22 @@ import UIKit final class ConnectViewController: BaseViewController { private lazy var completeView = CompleteView(state: .processing, title: l10n(.processing)) - + private var viewModel: ConnectViewModel + + init(viewModel: ConnectViewModel) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: .authenticator_main) + } + override func viewDidLoad() { super.viewDidLoad() - checkInternetConnection() + viewModel.checkInternetConnection() setupCancelButton() layout() } - - private func checkInternetConnection() { //TODO: Move logic to ViewModel, in controller we have logic only with UI -// guard ReachabilityManager.shared.isReachable else { -// self.showInfoAlert( -// withTitle: l10n(.noInternetConnection), -// message: l10n(.pleaseTryAgain), -// actionTitle: l10n(.ok), -// completion: { -// self.cancelPressed() -// } -// ) -// return -// } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") } } @@ -94,3 +90,18 @@ extension ConnectViewController: CompleteViewDelegate { cancelPressed() } } + +// MARK: - ConnectViewModelEventsDelegate +extension ConnectViewController: ConnectViewModelEventsDelegate { + func showNoInternetConnectionAlert() { + self.showInfoAlert( + withTitle: l10n(.noInternetConnection), + message: l10n(.pleaseTryAgain), + actionTitle: l10n(.ok), + completion: { + self.cancelPressed() + } + ) + } + +} diff --git a/Example/Authenticator/View Models/Connections/ConnectViewModel.swift b/Example/Authenticator/View Models/Connections/ConnectViewModel.swift new file mode 100644 index 00000000..138ac472 --- /dev/null +++ b/Example/Authenticator/View Models/Connections/ConnectViewModel.swift @@ -0,0 +1,49 @@ +// +// ConnectViewModel +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation + +protocol ConnectViewModelEventsDelegate: class { + func showNoInternetConnectionAlert() +} + +final class ConnectViewModel { + weak var delegate: ConnectViewModelEventsDelegate? + + private let reachabilityManager: ReachabilityManagerProtocol + + init(reachabilityManager: ReachabilityManagerProtocol) { + self.reachabilityManager = reachabilityManager + } +} + + +// MARK: - Delegate actions +extension ConnectViewModel { + func checkInternetConnection() { + guard reachabilityManager.isReachable else { + delegate?.showNoInternetConnectionAlert() + return + } + } +} + diff --git a/Example/Authenticator/View Models/Connections/ConnectionsViewModel.swift b/Example/Authenticator/View Models/Connections/ConnectionsViewModel.swift index e346ac49..bfd949fc 100644 --- a/Example/Authenticator/View Models/Connections/ConnectionsViewModel.swift +++ b/Example/Authenticator/View Models/Connections/ConnectionsViewModel.swift @@ -175,7 +175,7 @@ extension ConnectionsViewModel { func reconnect(id: ID) { delegate?.reconnect(by: id) } - + func checkInternetAndRemoveConnection(id: String, showConfirmation: Bool) { guard reachabilityManager.isReachable else { delegate?.showNoInternetConnectionAlert { @@ -183,8 +183,8 @@ extension ConnectionsViewModel { } return } - if showConfirmation { delegate?.showDeleteConfirmationAlert { self.remove(by: id) } } - else { remove(by: id) } + if showConfirmation { delegate?.showDeleteConfirmationAlert { self.remove(by: id) } + } else { remove(by: id) } } } From 3ac597b14e36ce0c5b07795bc34f597d1c6cb69c Mon Sep 17 00:00:00 2001 From: mnewlive Date: Fri, 6 Aug 2021 13:18:49 +0300 Subject: [PATCH 065/110] Added ConnectViewModelSpec --- .../Authenticator.xcodeproj/project.pbxproj | 4 + .../Connections/ConnectViewModel.swift | 2 - .../AuthorizationsViewModelSpec.swift | 2 +- .../ViewModels/ConnectViewModelSpec.swift | 77 +++++++++++++++++++ 4 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 Example/Tests/ViewModels/ConnectViewModelSpec.swift diff --git a/Example/Authenticator.xcodeproj/project.pbxproj b/Example/Authenticator.xcodeproj/project.pbxproj index b06424cd..77b8e30b 100644 --- a/Example/Authenticator.xcodeproj/project.pbxproj +++ b/Example/Authenticator.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 6CF5A0163F3E926E5B271FBE /* Pods_Authenticator_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC7966F04DE3EFEB4C47030C /* Pods_Authenticator_Tests.framework */; }; 8434188C26BD33180027CF1B /* ConnectViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8434188B26BD33180027CF1B /* ConnectViewModel.swift */; }; 8434188D26BD33180027CF1B /* ConnectViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8434188B26BD33180027CF1B /* ConnectViewModel.swift */; }; + 8434189026BD41A20027CF1B /* ConnectViewModelSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8434188E26BD41A20027CF1B /* ConnectViewModelSpec.swift */; }; 84726A0726B98538005C792E /* AddConnectionV2Fields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84726A0626B98538005C792E /* AddConnectionV2Fields.swift */; }; 84726A0826B98538005C792E /* AddConnectionV2Fields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84726A0626B98538005C792E /* AddConnectionV2Fields.swift */; }; 8480F53B26BC182200FB5DFF /* ConnectionsViewModelSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8480F53A26BC182200FB5DFF /* ConnectionsViewModelSpec.swift */; }; @@ -399,6 +400,7 @@ 607FACE51AFB9204008FA782 /* Authenticator_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Authenticator_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 8434188B26BD33180027CF1B /* ConnectViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectViewModel.swift; sourceTree = ""; }; + 8434188E26BD41A20027CF1B /* ConnectViewModelSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectViewModelSpec.swift; sourceTree = ""; }; 84726A0626B98538005C792E /* AddConnectionV2Fields.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddConnectionV2Fields.swift; sourceTree = ""; }; 8480F53A26BC182200FB5DFF /* ConnectionsViewModelSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionsViewModelSpec.swift; sourceTree = ""; }; 95033CC025F8FB0D00D64BDE /* MockLocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockLocationManager.swift; sourceTree = ""; }; @@ -972,6 +974,7 @@ children = ( 959E30AB247BDA030067354A /* Settings */, 9D8E5BA12445A1E70081E60E /* OnboardingViewModelSpec.swift */, + 8434188E26BD41A20027CF1B /* ConnectViewModelSpec.swift */, 9DD50990247527D800CB188D /* AuthorizationsViewModelSpec.swift */, 9DE5807C2450892E004AF19F /* PasscodeViewModelSpec.swift */, 8480F53A26BC182200FB5DFF /* ConnectionsViewModelSpec.swift */, @@ -2106,6 +2109,7 @@ 9D63AB5826715B4C007EE1C8 /* StringExtensionsSpec.swift in Sources */, 9D98746E24B3536400EE89BB /* ConsentSharedDataView.swift in Sources */, 9D1DEE7523D9C5D6003C79AC /* ConnectionPickerViewController.swift in Sources */, + 8434189026BD41A20027CF1B /* ConnectViewModelSpec.swift in Sources */, 9DBAD7A0247E55BD0023F944 /* QRCodeCoordinator.swift in Sources */, 9DE3245A22D3986B00EB162A /* RevokeConnectionResponseSpec.swift in Sources */, 9D772133231E51E40015BAC6 /* AuthorizationCollectionViewCell.swift in Sources */, diff --git a/Example/Authenticator/View Models/Connections/ConnectViewModel.swift b/Example/Authenticator/View Models/Connections/ConnectViewModel.swift index 138ac472..b54a15a0 100644 --- a/Example/Authenticator/View Models/Connections/ConnectViewModel.swift +++ b/Example/Authenticator/View Models/Connections/ConnectViewModel.swift @@ -36,7 +36,6 @@ final class ConnectViewModel { } } - // MARK: - Delegate actions extension ConnectViewModel { func checkInternetConnection() { @@ -46,4 +45,3 @@ extension ConnectViewModel { } } } - diff --git a/Example/Tests/ViewModels/AuthorizationsViewModelSpec.swift b/Example/Tests/ViewModels/AuthorizationsViewModelSpec.swift index 662ed2ad..26a2417e 100644 --- a/Example/Tests/ViewModels/AuthorizationsViewModelSpec.swift +++ b/Example/Tests/ViewModels/AuthorizationsViewModelSpec.swift @@ -41,7 +41,7 @@ final class AuthorizationsViewModelSpec: BaseSpec { describe("emptyViewData") { context("when there is no connections") { it("should return correct data") { - let viewModel = ConnectionsViewModelSpecAuthorizationsViewModel() + let viewModel = AuthorizationsViewModel() let dataSource = AuthorizationsDataSource() viewModel.dataSource = dataSource diff --git a/Example/Tests/ViewModels/ConnectViewModelSpec.swift b/Example/Tests/ViewModels/ConnectViewModelSpec.swift new file mode 100644 index 00000000..17367211 --- /dev/null +++ b/Example/Tests/ViewModels/ConnectViewModelSpec.swift @@ -0,0 +1,77 @@ +// +// ConnectViewModelSpec +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import Quick +import Nimble + +private final class MockSettingsDelegate: ConnectViewModelEventsDelegate { + + var showNoInternetConnectionAlertCall: Bool = false + + func showNoInternetConnectionAlert() { + showNoInternetConnectionAlertCall = true + } +} + +final class ConnectViewModelSpec: BaseSpec { + + private var viewModel: ConnectViewModel! + private var networkSpy: ReachabilityManagerSpy! + + override func spec() { + beforeEach { + self.networkSpy = ReachabilityManagerSpy() + self.viewModel = ConnectViewModel(reachabilityManager: self.networkSpy) + } + afterEach { + self.viewModel = nil + self.networkSpy = nil + } + + describe("checkInternetConnection") { + context("check internet connection when internet is off") { + it("should show internet connection alert") { + let mockDelegate = MockSettingsDelegate() + self.viewModel.delegate = mockDelegate + self.networkSpy.enabled = false + + self.viewModel.checkInternetConnection() + + expect(mockDelegate.showNoInternetConnectionAlertCall).to(beTrue()) + } + } + + context("check internet connection when internet is on") { + it("should show internet connection alert") { + let mockDelegate = MockSettingsDelegate() + self.viewModel.delegate = mockDelegate + self.networkSpy.enabled = true + + self.viewModel.checkInternetConnection() + + expect(mockDelegate.showNoInternetConnectionAlertCall).to(beFalse()) + } + } + } + } +} From 33b4abe84c7568ea919d48bfb3fc9f25ed9adb32 Mon Sep 17 00:00:00 2001 From: mnewlive Date: Fri, 6 Aug 2021 14:38:39 +0300 Subject: [PATCH 066/110] Resolved discussions --- .../Authenticator.xcodeproj/project.pbxproj | 12 ++-- .../Connect/InstantActionCoordinator.swift | 2 +- .../Coordinators/ConnectViewCoordinator.swift | 2 +- .../Connections/ConnectionsCoordinator.swift | 20 +++---- .../Supporting Files/AppDelegate.swift | 2 +- ...anager.swift => ConnectivityManager.swift} | 14 ++--- .../Connections/ConnectViewModel.swift | 6 +- .../Connections/ConnectionsViewModel.swift | 17 +++--- .../ViewModels/ConnectViewModelSpec.swift | 13 ++-- .../ViewModels/ConnectionsViewModelSpec.swift | 59 ++++++++----------- 10 files changed, 67 insertions(+), 80 deletions(-) rename Example/Authenticator/Utils/Helpers/{ReachabilityManager.swift => ConnectivityManager.swift} (92%) diff --git a/Example/Authenticator.xcodeproj/project.pbxproj b/Example/Authenticator.xcodeproj/project.pbxproj index 77b8e30b..86785d2c 100644 --- a/Example/Authenticator.xcodeproj/project.pbxproj +++ b/Example/Authenticator.xcodeproj/project.pbxproj @@ -246,8 +246,8 @@ 9DD4DF2923759767000A9B80 /* AVCaptureHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD4DF2723759767000A9B80 /* AVCaptureHelper.swift */; }; 9DD5098F24742ED900CB188D /* ConnectionsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95F408FF2473C90F00727095 /* ConnectionsCoordinator.swift */; }; 9DD50992247527D800CB188D /* AuthorizationsViewModelSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD50990247527D800CB188D /* AuthorizationsViewModelSpec.swift */; }; - 9DD8945D22E9D969008F3130 /* ReachabilityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD8945C22E9D969008F3130 /* ReachabilityManager.swift */; }; - 9DD8945E22E9D969008F3130 /* ReachabilityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD8945C22E9D969008F3130 /* ReachabilityManager.swift */; }; + 9DD8945D22E9D969008F3130 /* ConnectivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD8945C22E9D969008F3130 /* ConnectivityManager.swift */; }; + 9DD8945E22E9D969008F3130 /* ConnectivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD8945C22E9D969008F3130 /* ConnectivityManager.swift */; }; 9DD8946022E9F4BD008F3130 /* NotificationsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD8945F22E9F4BD008F3130 /* NotificationsHelper.swift */; }; 9DD8946122E9F4BD008F3130 /* NotificationsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD8945F22E9F4BD008F3130 /* NotificationsHelper.swift */; }; 9DE308B92445BB05009EBD39 /* Authenticator.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9DCED89522CE18280050ED3C /* Authenticator.strings */; }; @@ -573,7 +573,7 @@ 9DD4DF2723759767000A9B80 /* AVCaptureHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVCaptureHelper.swift; sourceTree = ""; }; 9DD50990247527D800CB188D /* AuthorizationsViewModelSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorizationsViewModelSpec.swift; sourceTree = ""; }; 9DD8158A22DF746300B93BA8 /* Authenticator_Example.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Authenticator_Example.entitlements; sourceTree = ""; }; - 9DD8945C22E9D969008F3130 /* ReachabilityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReachabilityManager.swift; sourceTree = ""; }; + 9DD8945C22E9D969008F3130 /* ConnectivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectivityManager.swift; sourceTree = ""; }; 9DD8945F22E9F4BD008F3130 /* NotificationsHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsHelper.swift; sourceTree = ""; }; 9DE308BA2446287A009EBD39 /* PasscodeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasscodeViewController.swift; sourceTree = ""; }; 9DE308BD24474D92009EBD39 /* PasscodeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasscodeViewModel.swift; sourceTree = ""; }; @@ -1162,7 +1162,7 @@ 9DCED83722CE18270050ED3C /* Localizations.swift */, 9DCED83A22CE18270050ED3C /* AppearanceHelper.swift */, 9DCED83D22CE18270050ED3C /* PasscodeManger.swift */, - 9DD8945C22E9D969008F3130 /* ReachabilityManager.swift */, + 9DD8945C22E9D969008F3130 /* ConnectivityManager.swift */, 9DD8945F22E9F4BD008F3130 /* NotificationsHelper.swift */, 9DCED83F22CE18270050ED3C /* NotificationsManager.swift */, 9DCED84022CE18270050ED3C /* HapticFeedbackHelper.swift */, @@ -1899,7 +1899,7 @@ 9DCED8D322CE18280050ED3C /* StackViewExtensions.swift in Sources */, 95C8C0EE25F272CC005E787F /* LocationManager.swift in Sources */, 9DCED91522CE18280050ED3C /* Connection.swift in Sources */, - 9DD8945D22E9D969008F3130 /* ReachabilityManager.swift in Sources */, + 9DD8945D22E9D969008F3130 /* ConnectivityManager.swift in Sources */, 9D87F4A7242A6BCD00F85D1F /* ConnectionsViewModel.swift in Sources */, 9DCED8AB22CE18280050ED3C /* SetupAppViewController.swift in Sources */, 9D98747324B353C000EE89BB /* ConsentAccountView.swift in Sources */, @@ -2068,7 +2068,7 @@ 9DE324A022D399FE00EB162A /* ViewExtensions+Animations.swift in Sources */, 9D8E5BA22445A1E70081E60E /* OnboardingViewModelSpec.swift in Sources */, 9DE308BF24474D92009EBD39 /* PasscodeViewModel.swift in Sources */, - 9DD8945E22E9D969008F3130 /* ReachabilityManager.swift in Sources */, + 9DD8945E22E9D969008F3130 /* ConnectivityManager.swift in Sources */, 9DD8946122E9F4BD008F3130 /* NotificationsHelper.swift in Sources */, 9DF7964D2498EFC6001290DA /* SingleAuthorizationViewModel.swift in Sources */, 9DE3249B22D399F200EB162A /* BundleExtensions.swift in Sources */, diff --git a/Example/Authenticator/Coordinators/Connect/InstantActionCoordinator.swift b/Example/Authenticator/Coordinators/Connect/InstantActionCoordinator.swift index 6a376911..1d014fdd 100644 --- a/Example/Authenticator/Coordinators/Connect/InstantActionCoordinator.swift +++ b/Example/Authenticator/Coordinators/Connect/InstantActionCoordinator.swift @@ -32,7 +32,7 @@ final class InstantActionCoordinator: Coordinator { init(rootViewController: UIViewController, qrUrl: URL) { self.rootViewController = rootViewController - let viewModel = ConnectViewModel(reachabilityManager: ReachabilityManager.shared) + let viewModel = ConnectViewModel(reachabilityManager: ConnectivityManager.shared) self.connectViewController = ConnectViewController(viewModel: viewModel) self.instantActionHandler = InstantActionHandler(qrUrl: qrUrl) if #available(iOS 13.0, *) { diff --git a/Example/Authenticator/Coordinators/ConnectViewCoordinator.swift b/Example/Authenticator/Coordinators/ConnectViewCoordinator.swift index 17bf5e63..96def99b 100644 --- a/Example/Authenticator/Coordinators/ConnectViewCoordinator.swift +++ b/Example/Authenticator/Coordinators/ConnectViewCoordinator.swift @@ -43,7 +43,7 @@ final class ConnectViewCoordinator: Coordinator { private let rootViewController: UIViewController private lazy var webViewController = ConnectorWebViewController() - private var viewModel = ConnectViewModel(reachabilityManager: ReachabilityManager.shared) + private var viewModel = ConnectViewModel(reachabilityManager: ConnectivityManager.shared) private lazy var connectViewController = ConnectViewController(viewModel: viewModel) private var qrCodeCoordinator: QRCodeCoordinator? private var connectHandler: ConnectHandler? diff --git a/Example/Authenticator/Coordinators/Connections/ConnectionsCoordinator.swift b/Example/Authenticator/Coordinators/Connections/ConnectionsCoordinator.swift index c312090f..581857b6 100644 --- a/Example/Authenticator/Coordinators/Connections/ConnectionsCoordinator.swift +++ b/Example/Authenticator/Coordinators/Connections/ConnectionsCoordinator.swift @@ -29,7 +29,7 @@ final class ConnectionsCoordinator: Coordinator { private var connectViewCoordinator: ConnectViewCoordinator? private var qrCodeCoordinator: QRCodeCoordinator? private var consentsCoordinator: ConsentsCoordinator? - private var viewModel = ConnectionsViewModel(reachabilityManager: ReachabilityManager.shared) + private var viewModel = ConnectionsViewModel(reachabilityManager: ConnectivityManager.shared) init(rootViewController: UIViewController) { self.rootViewController = rootViewController @@ -49,32 +49,28 @@ final class ConnectionsCoordinator: Coordinator { // MARK: - ConnectionsListEventsDelegate extension ConnectionsCoordinator: ConnectionsEventsDelegate { func showNoInternetConnectionAlert(completion:@escaping () -> Void) { - self.currentViewController.showConfirmationAlert( + currentViewController.showConfirmationAlert( withTitle: l10n(.noInternetConnection), message: l10n(.pleaseCheckAndTryAgain), confirmActionTitle: l10n(.retry), - confirmAction: { _ in - completion() - } + confirmAction: { _ in completion() } ) } - + func showDeleteConfirmationAlert(completion:@escaping () -> Void) { - self.currentViewController.showConfirmationAlert( + currentViewController.showConfirmationAlert( withTitle: l10n(.deleteConnection), message: l10n(.deleteConnectionDescription), confirmActionTitle: l10n(.delete), confirmActionStyle: .destructive, cancelTitle: l10n(.cancel), - confirmAction: { _ in - completion() - } + confirmAction: { _ in completion() } ) } - + func addPressed() { guard AVCaptureHelper.cameraIsAuthorized() else { - self.currentViewController.showConfirmationAlert( + currentViewController.showConfirmationAlert( withTitle: l10n(.deniedCamera), message: l10n(.deniedCameraDescription), confirmActionTitle: l10n(.goToSettings), diff --git a/Example/Authenticator/Supporting Files/AppDelegate.swift b/Example/Authenticator/Supporting Files/AppDelegate.swift index 3b2e8b19..c1199dd5 100644 --- a/Example/Authenticator/Supporting Files/AppDelegate.swift +++ b/Example/Authenticator/Supporting Files/AppDelegate.swift @@ -33,7 +33,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { self.window = UIWindow(frame: UIScreen.main.bounds) UNUserNotificationCenter.current().delegate = self - ReachabilityManager.shared.observeReachability() + ConnectivityManager.shared.observeReachability() AppearanceHelper.setup() CacheHelper.setDefaultDiskAge() configureFirebase() diff --git a/Example/Authenticator/Utils/Helpers/ReachabilityManager.swift b/Example/Authenticator/Utils/Helpers/ConnectivityManager.swift similarity index 92% rename from Example/Authenticator/Utils/Helpers/ReachabilityManager.swift rename to Example/Authenticator/Utils/Helpers/ConnectivityManager.swift index 88ab6278..846b21a5 100644 --- a/Example/Authenticator/Utils/Helpers/ReachabilityManager.swift +++ b/Example/Authenticator/Utils/Helpers/ConnectivityManager.swift @@ -23,12 +23,16 @@ import Foundation import Reachability -class ReachabilityManager: ReachabilityManagerProtocol { - static let shared = ReachabilityManager() +protocol Connectable { + var isConnected: Bool { get } +} + +class ConnectivityManager: Connectable { + static let shared = ConnectivityManager() private var reachability: Reachability! - var isReachable: Bool { + var isConnected: Bool { return reachability.connection != .unavailable } @@ -68,7 +72,3 @@ class ReachabilityManager: ReachabilityManagerProtocol { NotificationsHelper.removeObserver(self) } } - -protocol ReachabilityManagerProtocol { - var isReachable: Bool { get } -} diff --git a/Example/Authenticator/View Models/Connections/ConnectViewModel.swift b/Example/Authenticator/View Models/Connections/ConnectViewModel.swift index b54a15a0..299c00ec 100644 --- a/Example/Authenticator/View Models/Connections/ConnectViewModel.swift +++ b/Example/Authenticator/View Models/Connections/ConnectViewModel.swift @@ -29,9 +29,9 @@ protocol ConnectViewModelEventsDelegate: class { final class ConnectViewModel { weak var delegate: ConnectViewModelEventsDelegate? - private let reachabilityManager: ReachabilityManagerProtocol + private let reachabilityManager: Connectable - init(reachabilityManager: ReachabilityManagerProtocol) { + init(reachabilityManager: Connectable) { self.reachabilityManager = reachabilityManager } } @@ -39,7 +39,7 @@ final class ConnectViewModel { // MARK: - Delegate actions extension ConnectViewModel { func checkInternetConnection() { - guard reachabilityManager.isReachable else { + guard reachabilityManager.isConnected else { delegate?.showNoInternetConnectionAlert() return } diff --git a/Example/Authenticator/View Models/Connections/ConnectionsViewModel.swift b/Example/Authenticator/View Models/Connections/ConnectionsViewModel.swift index bfd949fc..1ee19f4f 100644 --- a/Example/Authenticator/View Models/Connections/ConnectionsViewModel.swift +++ b/Example/Authenticator/View Models/Connections/ConnectionsViewModel.swift @@ -47,9 +47,9 @@ final class ConnectionsViewModel { private var connectionsNotificationToken: NotificationToken? private var connectionsListener: RealmConnectionsListener? - private let reachabilityManager: ReachabilityManagerProtocol + private let reachabilityManager: Connectable - init(reachabilityManager: ReachabilityManagerProtocol) { + init(reachabilityManager: Connectable) { self.reachabilityManager = reachabilityManager connectionsListener = RealmConnectionsListener( onDataChange: { @@ -69,7 +69,7 @@ final class ConnectionsViewModel { var emptyViewData: EmptyViewData { return EmptyViewData( - image: UIImage(named: "noConnections", in: .authenticator_main, compatibleWith: nil)!, + image: UIImage(named: "noConnections", in: .authenticator_main, compatibleWith: nil) ?? UIImage(), title: l10n(.noConnections), description: l10n(.noConnectionsDescription), buttonTitle: l10n(.connect) @@ -177,14 +177,17 @@ extension ConnectionsViewModel { } func checkInternetAndRemoveConnection(id: String, showConfirmation: Bool) { - guard reachabilityManager.isReachable else { + guard reachabilityManager.isConnected else { delegate?.showNoInternetConnectionAlert { - if self.reachabilityManager.isReachable { self.remove(by: id) } + if self.reachabilityManager.isConnected { self.remove(by: id) } } return } - if showConfirmation { delegate?.showDeleteConfirmationAlert { self.remove(by: id) } - } else { remove(by: id) } + if showConfirmation { + delegate?.showDeleteConfirmationAlert { self.remove(by: id) } + } else { + remove(by: id) + } } } diff --git a/Example/Tests/ViewModels/ConnectViewModelSpec.swift b/Example/Tests/ViewModels/ConnectViewModelSpec.swift index 17367211..3810b9d0 100644 --- a/Example/Tests/ViewModels/ConnectViewModelSpec.swift +++ b/Example/Tests/ViewModels/ConnectViewModelSpec.swift @@ -20,12 +20,10 @@ // under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md // -import Foundation import Quick import Nimble -private final class MockSettingsDelegate: ConnectViewModelEventsDelegate { - +private final class MockСonnectDelegate: ConnectViewModelEventsDelegate { var showNoInternetConnectionAlertCall: Bool = false func showNoInternetConnectionAlert() { @@ -34,13 +32,12 @@ private final class MockSettingsDelegate: ConnectViewModelEventsDelegate { } final class ConnectViewModelSpec: BaseSpec { - private var viewModel: ConnectViewModel! - private var networkSpy: ReachabilityManagerSpy! + private var networkSpy: MockConnectable! override func spec() { beforeEach { - self.networkSpy = ReachabilityManagerSpy() + self.networkSpy = MockConnectable() self.viewModel = ConnectViewModel(reachabilityManager: self.networkSpy) } afterEach { @@ -51,7 +48,7 @@ final class ConnectViewModelSpec: BaseSpec { describe("checkInternetConnection") { context("check internet connection when internet is off") { it("should show internet connection alert") { - let mockDelegate = MockSettingsDelegate() + let mockDelegate = MockСonnectDelegate() self.viewModel.delegate = mockDelegate self.networkSpy.enabled = false @@ -63,7 +60,7 @@ final class ConnectViewModelSpec: BaseSpec { context("check internet connection when internet is on") { it("should show internet connection alert") { - let mockDelegate = MockSettingsDelegate() + let mockDelegate = MockСonnectDelegate() self.viewModel.delegate = mockDelegate self.networkSpy.enabled = true diff --git a/Example/Tests/ViewModels/ConnectionsViewModelSpec.swift b/Example/Tests/ViewModels/ConnectionsViewModelSpec.swift index 700e4b63..52ba2745 100644 --- a/Example/Tests/ViewModels/ConnectionsViewModelSpec.swift +++ b/Example/Tests/ViewModels/ConnectionsViewModelSpec.swift @@ -20,23 +20,19 @@ // under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md // -import Foundation import Quick import Nimble -import UIKit import SEAuthenticator -import RealmSwift @testable import SEAuthenticatorCore -class ReachabilityManagerSpy: ReachabilityManagerProtocol { +class MockConnectable: Connectable { var enabled = true - var isReachable: Bool { + var isConnected: Bool { return enabled } } -private final class MockSettingsDelegate: ConnectionsEventsDelegate { - +private final class MockConnectionsDelegate: ConnectionsEventsDelegate { var showEditConnectionAlertCall: Bool = false var showSupportCall: Bool = false var deleteConnectionCall: Bool = false @@ -90,13 +86,12 @@ private final class MockSettingsDelegate: ConnectionsEventsDelegate { } final class ConnectionsViewModelSpec: BaseSpec { - private var viewModel: ConnectionsViewModel! - private var networkSpy: ReachabilityManagerSpy! + private var networkSpy: MockConnectable! override func spec() { beforeEach { - self.networkSpy = ReachabilityManagerSpy() + self.networkSpy = MockConnectable() self.viewModel = ConnectionsViewModel(reachabilityManager: self.networkSpy) ConnectionRepository.deleteAllConnections() } @@ -132,42 +127,38 @@ final class ConnectionsViewModelSpec: BaseSpec { } describe("emptyViewData") { - context("when there is no connections") { - it("should return correct data") { - let expectedEmptyViewData = EmptyViewData( - image: UIImage(named: "noConnections", in: .authenticator_main, compatibleWith: nil)!, - title: l10n(.noConnections), - description: l10n(.noConnectionsDescription), - buttonTitle: l10n(.connect) - ) - - expect(self.viewModel.emptyViewData).to(equal(expectedEmptyViewData)) - } + it("should return correct data") { + let expectedEmptyViewData = EmptyViewData( + image: UIImage(named: "noConnections", in: .authenticator_main, compatibleWith: nil)!, + title: l10n(.noConnections), + description: l10n(.noConnectionsDescription), + buttonTitle: l10n(.connect) + ) + + expect(self.viewModel.emptyViewData).to(equal(expectedEmptyViewData)) } } describe("remove(id)") { - context("remove connections") { - it("should remove connection") { - let mockDelegate = MockSettingsDelegate() - self.viewModel.delegate = mockDelegate + it("should remove connection") { + let mockDelegate = MockConnectionsDelegate() + self.viewModel.delegate = mockDelegate - let connection = Connection() - connection.id = "id1" + let connection = Connection() + connection.id = "id1" - ConnectionRepository.save(connection) + ConnectionRepository.save(connection) - self.viewModel.remove(by: "id1") + self.viewModel.remove(by: "id1") - expect(self.viewModel.count).to(equal(0)) + expect(self.viewModel.count).to(equal(0)) } } - } describe("checkInternetAndRemoveConnection") { context("remove connection when internet is on and showConfirmation is true") { it("should remove connection") { - let mockDelegate = MockSettingsDelegate() + let mockDelegate = MockConnectionsDelegate() self.viewModel.delegate = mockDelegate let connection = Connection() @@ -184,7 +175,7 @@ final class ConnectionsViewModelSpec: BaseSpec { context("remove connection when internet is on and showConfirmation is false") { it("should remove connection") { - let mockDelegate = MockSettingsDelegate() + let mockDelegate = MockConnectionsDelegate() self.viewModel.delegate = mockDelegate let connection = Connection() @@ -201,7 +192,7 @@ final class ConnectionsViewModelSpec: BaseSpec { context("remove connection when internet is off") { it("should remove connection") { - let mockDelegate = MockSettingsDelegate() + let mockDelegate = MockConnectionsDelegate() self.viewModel.delegate = mockDelegate let connection = Connection() From 9e316da8a4444c45b0cfdba9ee30b260505bfa3d Mon Sep 17 00:00:00 2001 From: mnewlive Date: Fri, 6 Aug 2021 16:27:42 +0300 Subject: [PATCH 067/110] Resolved discussions --- .../Authenticator.xcodeproj/project.pbxproj | 4 +++ .../Connect/InstantActionCoordinator.swift | 3 +- .../Coordinators/ConnectViewCoordinator.swift | 3 +- .../Connections/ConnectionsCoordinator.swift | 4 +-- .../Connection/ConnectViewController.swift | 20 +++---------- .../Connections/ConnectionsViewModel.swift | 5 ++-- .../Tests/Spec Helpers/MockConnectable.swift | 30 +++++++++++++++++++ .../ViewModels/ConnectionsViewModelSpec.swift | 7 ----- 8 files changed, 45 insertions(+), 31 deletions(-) create mode 100644 Example/Tests/Spec Helpers/MockConnectable.swift diff --git a/Example/Authenticator.xcodeproj/project.pbxproj b/Example/Authenticator.xcodeproj/project.pbxproj index 86785d2c..1277dc02 100644 --- a/Example/Authenticator.xcodeproj/project.pbxproj +++ b/Example/Authenticator.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 8434188C26BD33180027CF1B /* ConnectViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8434188B26BD33180027CF1B /* ConnectViewModel.swift */; }; 8434188D26BD33180027CF1B /* ConnectViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8434188B26BD33180027CF1B /* ConnectViewModel.swift */; }; 8434189026BD41A20027CF1B /* ConnectViewModelSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8434188E26BD41A20027CF1B /* ConnectViewModelSpec.swift */; }; + 8434189226BD6FFB0027CF1B /* MockConnectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8434189126BD6FFB0027CF1B /* MockConnectable.swift */; }; 84726A0726B98538005C792E /* AddConnectionV2Fields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84726A0626B98538005C792E /* AddConnectionV2Fields.swift */; }; 84726A0826B98538005C792E /* AddConnectionV2Fields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84726A0626B98538005C792E /* AddConnectionV2Fields.swift */; }; 8480F53B26BC182200FB5DFF /* ConnectionsViewModelSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8480F53A26BC182200FB5DFF /* ConnectionsViewModelSpec.swift */; }; @@ -401,6 +402,7 @@ 607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 8434188B26BD33180027CF1B /* ConnectViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectViewModel.swift; sourceTree = ""; }; 8434188E26BD41A20027CF1B /* ConnectViewModelSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectViewModelSpec.swift; sourceTree = ""; }; + 8434189126BD6FFB0027CF1B /* MockConnectable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockConnectable.swift; sourceTree = ""; }; 84726A0626B98538005C792E /* AddConnectionV2Fields.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddConnectionV2Fields.swift; sourceTree = ""; }; 8480F53A26BC182200FB5DFF /* ConnectionsViewModelSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionsViewModelSpec.swift; sourceTree = ""; }; 95033CC025F8FB0D00D64BDE /* MockLocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockLocationManager.swift; sourceTree = ""; }; @@ -1432,6 +1434,7 @@ 9DE3246F22D398ED00EB162A /* BaseSpec.swift */, 9DE3247022D398ED00EB162A /* DataFixtures.swift */, 95033CC025F8FB0D00D64BDE /* MockLocationManager.swift */, + 8434189126BD6FFB0027CF1B /* MockConnectable.swift */, ); path = "Spec Helpers"; sourceTree = ""; @@ -2002,6 +2005,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 8434189226BD6FFB0027CF1B /* MockConnectable.swift in Sources */, 9D98747424B353C000EE89BB /* ConsentAccountView.swift in Sources */, 9D59DAF62490C11500117CD7 /* AuthorizationView.swift in Sources */, 959E30A3247BC7930067354A /* LanguagePickerCoordinator.swift in Sources */, diff --git a/Example/Authenticator/Coordinators/Connect/InstantActionCoordinator.swift b/Example/Authenticator/Coordinators/Connect/InstantActionCoordinator.swift index 1d014fdd..4791daec 100644 --- a/Example/Authenticator/Coordinators/Connect/InstantActionCoordinator.swift +++ b/Example/Authenticator/Coordinators/Connect/InstantActionCoordinator.swift @@ -32,8 +32,7 @@ final class InstantActionCoordinator: Coordinator { init(rootViewController: UIViewController, qrUrl: URL) { self.rootViewController = rootViewController - let viewModel = ConnectViewModel(reachabilityManager: ConnectivityManager.shared) - self.connectViewController = ConnectViewController(viewModel: viewModel) + self.connectViewController = ConnectViewController() self.instantActionHandler = InstantActionHandler(qrUrl: qrUrl) if #available(iOS 13.0, *) { connectViewController.isModalInPresentation = true diff --git a/Example/Authenticator/Coordinators/ConnectViewCoordinator.swift b/Example/Authenticator/Coordinators/ConnectViewCoordinator.swift index 96def99b..3f54fabf 100644 --- a/Example/Authenticator/Coordinators/ConnectViewCoordinator.swift +++ b/Example/Authenticator/Coordinators/ConnectViewCoordinator.swift @@ -43,8 +43,7 @@ final class ConnectViewCoordinator: Coordinator { private let rootViewController: UIViewController private lazy var webViewController = ConnectorWebViewController() - private var viewModel = ConnectViewModel(reachabilityManager: ConnectivityManager.shared) - private lazy var connectViewController = ConnectViewController(viewModel: viewModel) + private lazy var connectViewController = ConnectViewController() private var qrCodeCoordinator: QRCodeCoordinator? private var connectHandler: ConnectHandler? diff --git a/Example/Authenticator/Coordinators/Connections/ConnectionsCoordinator.swift b/Example/Authenticator/Coordinators/Connections/ConnectionsCoordinator.swift index 581857b6..3f9e8948 100644 --- a/Example/Authenticator/Coordinators/Connections/ConnectionsCoordinator.swift +++ b/Example/Authenticator/Coordinators/Connections/ConnectionsCoordinator.swift @@ -48,7 +48,7 @@ final class ConnectionsCoordinator: Coordinator { // MARK: - ConnectionsListEventsDelegate extension ConnectionsCoordinator: ConnectionsEventsDelegate { - func showNoInternetConnectionAlert(completion:@escaping () -> Void) { + func showNoInternetConnectionAlert(completion: @escaping () -> Void) { currentViewController.showConfirmationAlert( withTitle: l10n(.noInternetConnection), message: l10n(.pleaseCheckAndTryAgain), @@ -57,7 +57,7 @@ extension ConnectionsCoordinator: ConnectionsEventsDelegate { ) } - func showDeleteConfirmationAlert(completion:@escaping () -> Void) { + func showDeleteConfirmationAlert(completion: @escaping () -> Void) { currentViewController.showConfirmationAlert( withTitle: l10n(.deleteConnection), message: l10n(.deleteConnectionDescription), diff --git a/Example/Authenticator/View Controllers/Connection/ConnectViewController.swift b/Example/Authenticator/View Controllers/Connection/ConnectViewController.swift index ef749e2e..777c2404 100644 --- a/Example/Authenticator/View Controllers/Connection/ConnectViewController.swift +++ b/Example/Authenticator/View Controllers/Connection/ConnectViewController.swift @@ -24,23 +24,14 @@ import UIKit final class ConnectViewController: BaseViewController { private lazy var completeView = CompleteView(state: .processing, title: l10n(.processing)) - private var viewModel: ConnectViewModel - - init(viewModel: ConnectViewModel) { - self.viewModel = viewModel - super.init(nibName: nil, bundle: .authenticator_main) - } - + private let viewModel = ConnectViewModel(reachabilityManager: ConnectivityManager.shared) + override func viewDidLoad() { super.viewDidLoad() viewModel.checkInternetConnection() setupCancelButton() layout() } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } } // MARK: - Layout @@ -94,14 +85,11 @@ extension ConnectViewController: CompleteViewDelegate { // MARK: - ConnectViewModelEventsDelegate extension ConnectViewController: ConnectViewModelEventsDelegate { func showNoInternetConnectionAlert() { - self.showInfoAlert( + showInfoAlert( withTitle: l10n(.noInternetConnection), message: l10n(.pleaseTryAgain), actionTitle: l10n(.ok), - completion: { - self.cancelPressed() - } + completion: { self.cancelPressed() } ) } - } diff --git a/Example/Authenticator/View Models/Connections/ConnectionsViewModel.swift b/Example/Authenticator/View Models/Connections/ConnectionsViewModel.swift index 1ee19f4f..66523f67 100644 --- a/Example/Authenticator/View Models/Connections/ConnectionsViewModel.swift +++ b/Example/Authenticator/View Models/Connections/ConnectionsViewModel.swift @@ -34,8 +34,8 @@ protocol ConnectionsEventsDelegate: class { func updateViews() func addPressed() func presentError(_ error: String) - func showNoInternetConnectionAlert(completion:@escaping () -> Void) - func showDeleteConfirmationAlert(completion:@escaping () -> Void) + func showNoInternetConnectionAlert(completion: @escaping () -> Void) + func showDeleteConfirmationAlert(completion: @escaping () -> Void) } final class ConnectionsViewModel { @@ -183,6 +183,7 @@ extension ConnectionsViewModel { } return } + if showConfirmation { delegate?.showDeleteConfirmationAlert { self.remove(by: id) } } else { diff --git a/Example/Tests/Spec Helpers/MockConnectable.swift b/Example/Tests/Spec Helpers/MockConnectable.swift new file mode 100644 index 00000000..0644221b --- /dev/null +++ b/Example/Tests/Spec Helpers/MockConnectable.swift @@ -0,0 +1,30 @@ +// +// MockConnectable +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation + +class MockConnectable: Connectable { + var enabled = true + var isConnected: Bool { + return enabled + } +} diff --git a/Example/Tests/ViewModels/ConnectionsViewModelSpec.swift b/Example/Tests/ViewModels/ConnectionsViewModelSpec.swift index 52ba2745..2a2e4452 100644 --- a/Example/Tests/ViewModels/ConnectionsViewModelSpec.swift +++ b/Example/Tests/ViewModels/ConnectionsViewModelSpec.swift @@ -25,13 +25,6 @@ import Nimble import SEAuthenticator @testable import SEAuthenticatorCore -class MockConnectable: Connectable { - var enabled = true - var isConnected: Bool { - return enabled - } -} - private final class MockConnectionsDelegate: ConnectionsEventsDelegate { var showEditConnectionAlertCall: Bool = false var showSupportCall: Bool = false From ec5eceaa7feb0b85ac68545ff4e60d0cf37a703b Mon Sep 17 00:00:00 2001 From: mnewlive Date: Fri, 6 Aug 2021 16:30:07 +0300 Subject: [PATCH 068/110] Clean code --- .../View Controllers/ConnectorWebViewController.swift | 1 - .../View Models/Connections/ConnectionCellViewModel.swift | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Example/Authenticator/View Controllers/ConnectorWebViewController.swift b/Example/Authenticator/View Controllers/ConnectorWebViewController.swift index a0ebb6db..38b61559 100644 --- a/Example/Authenticator/View Controllers/ConnectorWebViewController.swift +++ b/Example/Authenticator/View Controllers/ConnectorWebViewController.swift @@ -23,7 +23,6 @@ import UIKit import WebKit import SEAuthenticatorCore -//import SEAuthenticator protocol ConnectorWebViewControllerDelegate: WKWebViewControllerDelegate { func connectorConfirmed(url: URL, accessToken: AccessToken) diff --git a/Example/Authenticator/View Models/Connections/ConnectionCellViewModel.swift b/Example/Authenticator/View Models/Connections/ConnectionCellViewModel.swift index 4bd01123..82ef3e76 100644 --- a/Example/Authenticator/View Models/Connections/ConnectionCellViewModel.swift +++ b/Example/Authenticator/View Models/Connections/ConnectionCellViewModel.swift @@ -123,7 +123,7 @@ class ConnectionCellViewModel { } ) } - + actions.append( UIAction( title: "\(l10n(.id)) \(strongSelf.connection.id)", From 480f62c16591e436317e9712d95d4cf1ce56fba1 Mon Sep 17 00:00:00 2001 From: mnewlive Date: Fri, 6 Aug 2021 17:49:22 +0300 Subject: [PATCH 069/110] Resolved discussions --- .../ViewModels/ConnectionsViewModelSpec.swift | 81 ++++++++++++------- 1 file changed, 52 insertions(+), 29 deletions(-) diff --git a/Example/Tests/ViewModels/ConnectionsViewModelSpec.swift b/Example/Tests/ViewModels/ConnectionsViewModelSpec.swift index 2a2e4452..c3657915 100644 --- a/Example/Tests/ViewModels/ConnectionsViewModelSpec.swift +++ b/Example/Tests/ViewModels/ConnectionsViewModelSpec.swift @@ -79,18 +79,18 @@ private final class MockConnectionsDelegate: ConnectionsEventsDelegate { } final class ConnectionsViewModelSpec: BaseSpec { - private var viewModel: ConnectionsViewModel! - private var networkSpy: MockConnectable! - override func spec() { + var viewModel: ConnectionsViewModel! + var networkSpy: MockConnectable! + beforeEach { - self.networkSpy = MockConnectable() - self.viewModel = ConnectionsViewModel(reachabilityManager: self.networkSpy) + networkSpy = MockConnectable() + viewModel = ConnectionsViewModel(reachabilityManager: networkSpy) ConnectionRepository.deleteAllConnections() } afterEach { - self.viewModel = nil - self.networkSpy = nil + viewModel = nil + networkSpy = nil } describe("count") { @@ -100,7 +100,7 @@ final class ConnectionsViewModelSpec: BaseSpec { ConnectionRepository.save(connection) - expect(self.viewModel.count).to(equal(1)) + expect(viewModel.count).to(equal(1)) } } @@ -111,11 +111,11 @@ final class ConnectionsViewModelSpec: BaseSpec { ConnectionRepository.save(connection) - expect(self.viewModel.hasDataToShow).to(equal(true)) + expect(viewModel.hasDataToShow).to(equal(true)) } it("will not display the data to be displayed") { - expect(self.viewModel.hasDataToShow).to(equal(false)) + expect(viewModel.hasDataToShow).to(equal(false)) } } @@ -128,76 +128,99 @@ final class ConnectionsViewModelSpec: BaseSpec { buttonTitle: l10n(.connect) ) - expect(self.viewModel.emptyViewData).to(equal(expectedEmptyViewData)) + expect(viewModel.emptyViewData).to(equal(expectedEmptyViewData)) } } describe("remove(id)") { it("should remove connection") { let mockDelegate = MockConnectionsDelegate() - self.viewModel.delegate = mockDelegate + viewModel.delegate = mockDelegate let connection = Connection() connection.id = "id1" ConnectionRepository.save(connection) - self.viewModel.remove(by: "id1") + viewModel.remove(by: "id1") - expect(self.viewModel.count).to(equal(0)) + expect(viewModel.count).to(equal(0)) } } describe("checkInternetAndRemoveConnection") { - context("remove connection when internet is on and showConfirmation is true") { - it("should remove connection") { + context("check internet connection and if internet is off and showConfirmation is true") { + it("should display no internet connection alert") { let mockDelegate = MockConnectionsDelegate() - self.viewModel.delegate = mockDelegate + viewModel.delegate = mockDelegate let connection = Connection() connection.id = "id1" ConnectionRepository.save(connection) - self.networkSpy.enabled = false + networkSpy.enabled = false - self.viewModel.checkInternetAndRemoveConnection(id: "id1", showConfirmation: true) + viewModel.checkInternetAndRemoveConnection(id: "id1", showConfirmation: true) expect(mockDelegate.showNoInternetConnectionAlertCall).to(beTrue()) + expect(mockDelegate.showDeleteConfirmationAlertCall).to(beFalse()) } } - context("remove connection when internet is on and showConfirmation is false") { - it("should remove connection") { + context("check internet connection and if internet is off and showConfirmation is false") { + it("should display no internet connection alert") { let mockDelegate = MockConnectionsDelegate() - self.viewModel.delegate = mockDelegate + viewModel.delegate = mockDelegate let connection = Connection() connection.id = "id1" ConnectionRepository.save(connection) - self.networkSpy.enabled = false + networkSpy.enabled = false - self.viewModel.checkInternetAndRemoveConnection(id: "id1", showConfirmation: false) + viewModel.checkInternetAndRemoveConnection(id: "id1", showConfirmation: false) expect(mockDelegate.showNoInternetConnectionAlertCall).to(beTrue()) + expect(mockDelegate.showDeleteConfirmationAlertCall).to(beFalse()) } } - context("remove connection when internet is off") { - it("should remove connection") { + context("check internet connection and if internet is on and showConfirmation is true") { + it("should display delete confirmation alert") { let mockDelegate = MockConnectionsDelegate() - self.viewModel.delegate = mockDelegate + viewModel.delegate = mockDelegate let connection = Connection() connection.id = "id1" ConnectionRepository.save(connection) - self.networkSpy.enabled = true + networkSpy.enabled = true - self.viewModel.checkInternetAndRemoveConnection(id: "id1", showConfirmation: true) + viewModel.checkInternetAndRemoveConnection(id: "id1", showConfirmation: true) expect(mockDelegate.showDeleteConfirmationAlertCall).to(beTrue()) + expect(mockDelegate.showNoInternetConnectionAlertCall).to(beFalse()) + } + } + + context("check internet connection and if internet is on and showConfirmation is false") { + it("should remove connection") { + let mockDelegate = MockConnectionsDelegate() + viewModel.delegate = mockDelegate + + let connection = Connection() + connection.id = "id1" + + ConnectionRepository.save(connection) + + networkSpy.enabled = true + + viewModel.checkInternetAndRemoveConnection(id: "id1", showConfirmation: false) + + expect(viewModel.count).to(equal(0)) + expect(mockDelegate.showDeleteConfirmationAlertCall).to(beFalse()) + expect(mockDelegate.showNoInternetConnectionAlertCall).to(beFalse()) } } } From 720bae753e617236bcbaccd896440704cccd1d0e Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Mon, 9 Aug 2021 17:54:55 +0300 Subject: [PATCH 070/110] converted requests ids output type to int in v2 --- .../Classes/API/Models/Responses/SEProviderResponse.swift | 1 - .../API/Responses/SEConfirmAuthorizationResponseV2.swift | 4 ++-- .../API/Responses/SECreateConnectionResponse.swift | 4 ++-- .../Classes/API/Responses/SEProviderResponseV2.swift | 4 ++-- .../API/Responses/SERevokeConnectionResponse.swift | 4 ++-- .../Classes/API/Responses/SERevokeConsentResponseV2.swift | 4 ++-- .../Classes/API/Responses/SESubmitActionResponseV2.swift | 8 ++++---- 7 files changed, 14 insertions(+), 15 deletions(-) diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SEProviderResponse.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SEProviderResponse.swift index 0f28bb96..0a3619ce 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SEProviderResponse.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SEProviderResponse.swift @@ -40,7 +40,6 @@ public struct SEProviderResponse: SerializableResponse { let connectUrlString = data[SENetKeys.connectUrl] as? String, let version = data[SENetKeys.version] as? String, let connectUrl = URL(string: connectUrlString) { - if let logoUrlString = data[SENetKeys.logoUrl] as? String, let logoUrl = URL(string: logoUrlString) { self.logoUrl = logoUrl diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEConfirmAuthorizationResponseV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEConfirmAuthorizationResponseV2.swift index a79506c4..5c44cda9 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEConfirmAuthorizationResponseV2.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEConfirmAuthorizationResponseV2.swift @@ -32,8 +32,8 @@ public struct SEConfirmAuthorizationResponseV2: SerializableResponse { let data = dict[SENetKeys.data] as? [String: Any], let statusString = data[SENetKeys.status] as? String, let status = AuthorizationStatus(rawValue: statusString), - let id = data[SENetKeys.id] as? String { - self.id = id + let id = data[SENetKeys.id] as? Int { + self.id = "\(id)" self.status = status } else { return nil diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SECreateConnectionResponse.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SECreateConnectionResponse.swift index 865b807f..722cf204 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SECreateConnectionResponse.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SECreateConnectionResponse.swift @@ -30,9 +30,9 @@ public struct SECreateConnectionResponse: SerializableResponse { public init?(_ value: Any) { if let dict = value as? [String: Any], let dataDict = dict[SENetKeys.data] as? [String: Any], - let id = dataDict[SENetKeys.connectionId] as? String, + let id = dataDict[SENetKeys.connectionId] as? Int, let authenticationUrl = dataDict[ApiConstants.authenticationUrl] as? String { - self.id = id + self.id = "\(id)" self.authenticationUrl = authenticationUrl } else { return nil diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponseV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponseV2.swift index 01552c8f..cd431d85 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponseV2.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponseV2.swift @@ -36,7 +36,7 @@ public struct SEProviderResponseV2: SerializableResponse { public init?(_ value: Any) { if let dict = value as? [String: Any], let dataDict = dict[SENetKeys.data] as? [String: Any], - let id = dataDict[ApiConstants.providerId] as? String, + let id = dataDict[ApiConstants.providerId] as? Int, let name = dataDict[ApiConstants.providerName] as? String, let scaServiceUrlString = dataDict[ApiConstants.scaServiceUrl] as? String, let apiVersion = dataDict[ApiConstants.apiVersion] as? String, @@ -49,7 +49,7 @@ public struct SEProviderResponseV2: SerializableResponse { self.supportEmail = (dataDict[ApiConstants.providerSupportEmail] as? String) ?? "" self.geolocationRequired = dataDict[SENetKeys.geolocationRequired] as? Bool - self.providerId = id + self.providerId = "\(id)" self.name = name self.baseUrl = scaServiceUrl self.apiVersion = apiVersion diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConnectionResponse.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConnectionResponse.swift index 220267fb..32b88fb1 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConnectionResponse.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConnectionResponse.swift @@ -29,8 +29,8 @@ public struct SERevokeConnectionResponse: SerializableResponse { public init?(_ value: Any) { if let dict = value as? [String: Any], let dataDict = dict[SENetKeys.data] as? [String: Any], - let connectionId = dataDict[SENetKeys.connectionId] as? String { - self.connectionId = connectionId + let connectionId = dataDict[SENetKeys.connectionId] as? Int { + self.connectionId = "\(connectionId)" } else { return nil } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConsentResponseV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConsentResponseV2.swift index 5a0305fc..05a48e74 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConsentResponseV2.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConsentResponseV2.swift @@ -29,8 +29,8 @@ public struct SERevokeConsentResponseV2: SerializableResponse { public init?(_ value: Any) { if let dict = value as? [String: Any], let data = dict[SENetKeys.data] as? [String: Any], - let id = data[SENetKeys.id] as? String { - self.id = id + let id = data[SENetKeys.id] as? Int { + self.id = "\(id)" } else { return nil } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SESubmitActionResponseV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SESubmitActionResponseV2.swift index 50810727..4df6b1a6 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SESubmitActionResponseV2.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SESubmitActionResponseV2.swift @@ -30,10 +30,10 @@ public struct SESubmitActionResponseV2: SEBaseActionResponse { public init?(_ value: Any) { if let dict = value as? [String: Any], let data = dict[SENetKeys.data] as? [String: Any], - let authorizationId = data[SENetKeys.authorizationId] as? String, - let connectionId = data[SENetKeys.connectionId] as? String { - self.authorizationId = authorizationId - self.connectionId = connectionId + let authorizationId = data[SENetKeys.authorizationId] as? Int, + let connectionId = data[SENetKeys.connectionId] as? Int { + self.authorizationId = "\(authorizationId)" + self.connectionId = "\(connectionId)" } else { return nil } From 3955398ceaf5eda3042a1416928292ddf4d8b5f6 Mon Sep 17 00:00:00 2001 From: mnewlive Date: Wed, 18 Aug 2021 11:03:35 +0300 Subject: [PATCH 071/110] Added a check for the closed status --- .../Models/Data Sources/AuthorizationsDataSource.swift | 2 +- .../Classes/Helpers/AuthorizationStatus.swift | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift b/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift index 1e24824b..c1e7c869 100644 --- a/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift +++ b/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift @@ -150,7 +150,7 @@ private extension Array where Element == SEBaseAuthorizationData { v2Response.status.isFinal, let filtered = existedViewModels.filter({ $0.authorizationId == v2Response.id }).first { guard filtered.authorizationExpiresAt >= Date(), - let filteredStatus = filtered.status, !filteredStatus.isFinal else { return nil } + let filteredStatus = filtered.status, !filteredStatus.isFinal, !filteredStatus.isClosed else { return nil } filtered.setFinal(status: v2Response.status) return filtered diff --git a/SaltedgeAuthenticatorCore/Classes/Helpers/AuthorizationStatus.swift b/SaltedgeAuthenticatorCore/Classes/Helpers/AuthorizationStatus.swift index 53f44794..8ebde3cf 100644 --- a/SaltedgeAuthenticatorCore/Classes/Helpers/AuthorizationStatus.swift +++ b/SaltedgeAuthenticatorCore/Classes/Helpers/AuthorizationStatus.swift @@ -32,6 +32,7 @@ public enum AuthorizationStatus: String { case unavailable case confirmProcessing = "confirm_processing" case denyProcessing = "deny_processing" + case closed = "closed" public var isFinal: Bool { return self == .confirmed @@ -44,4 +45,8 @@ public enum AuthorizationStatus: String { public var isProcessing: Bool { return self == .confirmProcessing || self == .denyProcessing } + + public var isClosed: Bool { + return self == .closed + } } From 01b6ab3ab8cf652ea875d269654133fe5e705ec9 Mon Sep 17 00:00:00 2001 From: mnewlive Date: Thu, 19 Aug 2021 11:06:30 +0300 Subject: [PATCH 072/110] Added tests for AuthorizationStatus --- .../Authenticator.xcodeproj/project.pbxproj | 4 + .../Helpers/AuthorizationStatusSpec.swift | 169 ++++++++++++++++++ .../Classes/Helpers/AuthorizationStatus.swift | 2 +- 3 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 Example/Tests/Helpers/AuthorizationStatusSpec.swift diff --git a/Example/Authenticator.xcodeproj/project.pbxproj b/Example/Authenticator.xcodeproj/project.pbxproj index 1277dc02..0383b9f7 100644 --- a/Example/Authenticator.xcodeproj/project.pbxproj +++ b/Example/Authenticator.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 0479C5522387F8C300F3FE0A /* AVCaptureHelperSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0479C5512387F8C300F3FE0A /* AVCaptureHelperSpec.swift */; }; 6CF5A0163F3E926E5B271FBE /* Pods_Authenticator_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC7966F04DE3EFEB4C47030C /* Pods_Authenticator_Tests.framework */; }; + 8432878826CE378400F77DD5 /* AuthorizationStatusSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8432878726CE378400F77DD5 /* AuthorizationStatusSpec.swift */; }; 8434188C26BD33180027CF1B /* ConnectViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8434188B26BD33180027CF1B /* ConnectViewModel.swift */; }; 8434188D26BD33180027CF1B /* ConnectViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8434188B26BD33180027CF1B /* ConnectViewModel.swift */; }; 8434189026BD41A20027CF1B /* ConnectViewModelSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8434188E26BD41A20027CF1B /* ConnectViewModelSpec.swift */; }; @@ -400,6 +401,7 @@ 607FACD01AFB9204008FA782 /* Authenticator_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Authenticator_Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 607FACE51AFB9204008FA782 /* Authenticator_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Authenticator_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 8432878726CE378400F77DD5 /* AuthorizationStatusSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorizationStatusSpec.swift; sourceTree = ""; }; 8434188B26BD33180027CF1B /* ConnectViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectViewModel.swift; sourceTree = ""; }; 8434188E26BD41A20027CF1B /* ConnectViewModelSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectViewModelSpec.swift; sourceTree = ""; }; 8434189126BD6FFB0027CF1B /* MockConnectable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockConnectable.swift; sourceTree = ""; }; @@ -827,6 +829,7 @@ 95CD02DC235864130085C52A /* SEConnectHelperSpec.swift */, 0479C5512387F8C300F3FE0A /* AVCaptureHelperSpec.swift */, 9DD155EB265E74C800AEFA0D /* ApiVersionExtractorSpec.swift */, + 8432878726CE378400F77DD5 /* AuthorizationStatusSpec.swift */, ); path = Helpers; sourceTree = ""; @@ -2081,6 +2084,7 @@ 9DD4DF2923759767000A9B80 /* AVCaptureHelper.swift in Sources */, 9D69071C2655439200A9CE94 /* JWSHelperSpec.swift in Sources */, 9DE3249922D399ED00EB162A /* StringExtensions.swift in Sources */, + 8432878826CE378400F77DD5 /* AuthorizationStatusSpec.swift in Sources */, 9D66E1DE22D6146900BD59B6 /* ConnectionRouterSpec.swift in Sources */, 8480F53B26BC182200FB5DFF /* ConnectionsViewModelSpec.swift in Sources */, 9DE324CE22D39A9800EB162A /* CustomSpacingLabel.swift in Sources */, diff --git a/Example/Tests/Helpers/AuthorizationStatusSpec.swift b/Example/Tests/Helpers/AuthorizationStatusSpec.swift new file mode 100644 index 00000000..8ddb8e21 --- /dev/null +++ b/Example/Tests/Helpers/AuthorizationStatusSpec.swift @@ -0,0 +1,169 @@ +// +// AuthorizationStatusSpec +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Quick +import Nimble +@testable import SEAuthenticatorCore + +final class AuthorizationStatusSpec: BaseSpec { + override func spec() { + var authorizationStatus: AuthorizationStatus! + + describe("isClosed") { + it("should return true if authorization status is closed") { + authorizationStatus = .closed + + expect(authorizationStatus.isClosed).to(equal(true)) + } + + it("should return false if authorization status is closed") { + authorizationStatus = .pending + + expect(authorizationStatus.isClosed).to(equal(false)) + + authorizationStatus = .processing + + expect(authorizationStatus.isClosed).to(equal(false)) + + authorizationStatus = .confirmed + + expect(authorizationStatus.isClosed).to(equal(false)) + + authorizationStatus = .denied + + expect(authorizationStatus.isClosed).to(equal(false)) + + authorizationStatus = .error + + expect(authorizationStatus.isClosed).to(equal(false)) + + authorizationStatus = .timeOut + + expect(authorizationStatus.isClosed).to(equal(false)) + + authorizationStatus = .unavailable + + expect(authorizationStatus.isClosed).to(equal(false)) + + authorizationStatus = .confirmProcessing + + expect(authorizationStatus.isClosed).to(equal(false)) + + authorizationStatus = .denyProcessing + + expect(authorizationStatus.isClosed).to(equal(false)) + } + } + + describe("isProcessing") { + it("should return true if authorization status is processing") { + authorizationStatus = .confirmProcessing + + expect(authorizationStatus.isProcessing).to(equal(true)) + + authorizationStatus = .denyProcessing + + expect(authorizationStatus.isProcessing).to(equal(true)) + } + + it("should return false if authorization status is processing") { + authorizationStatus = .pending + + expect(authorizationStatus.isProcessing).to(equal(false)) + + authorizationStatus = .processing + + expect(authorizationStatus.isProcessing).to(equal(false)) + + authorizationStatus = .confirmed + + expect(authorizationStatus.isProcessing).to(equal(false)) + + authorizationStatus = .denied + + expect(authorizationStatus.isProcessing).to(equal(false)) + + authorizationStatus = .error + + expect(authorizationStatus.isProcessing).to(equal(false)) + + authorizationStatus = .timeOut + + expect(authorizationStatus.isProcessing).to(equal(false)) + + authorizationStatus = .unavailable + + expect(authorizationStatus.isProcessing).to(equal(false)) + + authorizationStatus = .closed + + expect(authorizationStatus.isProcessing).to(equal(false)) + } + } + + describe("isFinal") { + it("should return true if authorization status is final") { + authorizationStatus = .confirmed + + expect(authorizationStatus.isFinal).to(equal(true)) + + authorizationStatus = .denied + + expect(authorizationStatus.isFinal).to(equal(true)) + + authorizationStatus = .error + + expect(authorizationStatus.isFinal).to(equal(true)) + + authorizationStatus = .timeOut + + expect(authorizationStatus.isFinal).to(equal(true)) + + authorizationStatus = .unavailable + + expect(authorizationStatus.isFinal).to(equal(true)) + } + + it("should return false if authorization status is final") { + authorizationStatus = .pending + + expect(authorizationStatus.isFinal).to(equal(false)) + + authorizationStatus = .processing + + expect(authorizationStatus.isFinal).to(equal(false)) + + authorizationStatus = .confirmProcessing + + expect(authorizationStatus.isFinal).to(equal(false)) + + authorizationStatus = .denyProcessing + + expect(authorizationStatus.isFinal).to(equal(false)) + + authorizationStatus = .closed + + expect(authorizationStatus.isFinal).to(equal(false)) + } + } + } +} diff --git a/SaltedgeAuthenticatorCore/Classes/Helpers/AuthorizationStatus.swift b/SaltedgeAuthenticatorCore/Classes/Helpers/AuthorizationStatus.swift index 8ebde3cf..82f40d52 100644 --- a/SaltedgeAuthenticatorCore/Classes/Helpers/AuthorizationStatus.swift +++ b/SaltedgeAuthenticatorCore/Classes/Helpers/AuthorizationStatus.swift @@ -47,6 +47,6 @@ public enum AuthorizationStatus: String { } public var isClosed: Bool { - return self == .closed + return self == .closed } } From aa03350300b67172184e2b315893ff4f04e15354 Mon Sep 17 00:00:00 2001 From: mnewlive Date: Thu, 19 Aug 2021 20:09:44 +0300 Subject: [PATCH 073/110] Added test for AuthorizationsDataSourceSpec --- .../SEEncryptedDataExtensions.swift | 2 +- .../AuthorizationsDataSourceSpec.swift | 41 ++++++++++++++++++- Example/Tests/Spec Helpers/SpecUtils.swift | 12 +++--- 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/Example/Authenticator/Utils/Extensions/SEEncryptedDataExtensions.swift b/Example/Authenticator/Utils/Extensions/SEEncryptedDataExtensions.swift index 088d657e..3cbf4e66 100644 --- a/Example/Authenticator/Utils/Extensions/SEEncryptedDataExtensions.swift +++ b/Example/Authenticator/Utils/Extensions/SEEncryptedDataExtensions.swift @@ -37,7 +37,7 @@ extension SEBaseEncryptedAuthorizationData { guard let v2Response = self as? SEEncryptedAuthorizationData, let connectionId = connectionId else { return nil } - if v2Response.status.isFinal { + if v2Response.status.isFinal || v2Response.status.isClosed { return SEAuthorizationDataV2( id: v2Response.id, connectionId: connectionId, diff --git a/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift b/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift index 1985aa64..bcbc2fbf 100644 --- a/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift +++ b/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift @@ -226,7 +226,7 @@ class AuthorizationsDataSourceSpec: BaseSpec { expect(dataSource.rows).to(equal(1)) - let finalStatusAuthorization = SpecUtils.createFinalAuthResponseV2( + let finalStatusAuthorization = SpecUtils.createNotEncryptedAuthResponseV2( with: authMessage, authorizationId: 909, connectionId: Int(connection.id)!, @@ -241,6 +241,45 @@ class AuthorizationsDataSourceSpec: BaseSpec { } } + + describe("toAuthorizationViewModels with closed state") { + it("when recieved authorization with closed state we should skip it") { + expect(dataSource.rows).to(equal(2)) + + let authMessage: [String: Any] = [ + "title": "Authorization V2", + "authorization_code": "code", + "description": ["text": "Test valid authorization"], + "created_at": Date().iso8601string, + "expires_at": Date().addingTimeInterval(3.0 * 60.0).iso8601string + ] + + let decryptedData = SpecUtils.createAuthResponseV2( + with: authMessage, + authorizationId: 909, + connectionId: Int(connection.id)!, + guid: connection.guid, + status: "closed" + ) + + _ = dataSource.update(with: [decryptedData]) + + expect(dataSource.rows).to(equal(0)) + + let closedStatusAuthorization = SpecUtils.createNotEncryptedAuthResponseV2( + with: authMessage, + authorizationId: 909, + connectionId: Int(connection.id)!, + guid: connection.guid, + status: "closed" + ) + + _ = dataSource.update(with: [closedStatusAuthorization]) + + expect(dataSource.rows).to(equal(0)) + } + } + describe("clearAuthoriations") { it("should remove all authorizations") { expect(dataSource.rows).to(equal(2)) diff --git a/Example/Tests/Spec Helpers/SpecUtils.swift b/Example/Tests/Spec Helpers/SpecUtils.swift index e753625f..bb1e4c0f 100644 --- a/Example/Tests/Spec Helpers/SpecUtils.swift +++ b/Example/Tests/Spec Helpers/SpecUtils.swift @@ -65,7 +65,8 @@ struct SpecUtils { with authMessage: [String: Any], authorizationId: Int, connectionId: Int, - guid: GUID + guid: GUID, + status: String = "pending" ) -> SEAuthorizationDataV2 { let encryptedData = try! SECryptoHelper.encrypt(authMessage.jsonString!, tag: SETagHelper.create(for: guid)) @@ -75,17 +76,18 @@ struct SpecUtils { "iv": encryptedData.iv, "id": authorizationId, "connection_id": connectionId, - "status": "pending" + "status": status ] return SEEncryptedAuthorizationData(dict)!.decryptedAuthorizationDataV2! } - static func createFinalAuthResponseV2( + static func createNotEncryptedAuthResponseV2( with authMessage: [String: Any], authorizationId: Int, connectionId: Int, - guid: GUID + guid: GUID, + status: String = "denied" ) -> SEAuthorizationDataV2 { let dict: [String: Any] = [ "data": "", @@ -93,7 +95,7 @@ struct SpecUtils { "iv": "", "id": authorizationId, "connection_id": connectionId, - "status": "denied" + "status": status ] return SEEncryptedAuthorizationData(dict)!.decryptedAuthorizationDataV2! From 3a9813a3f3b5f93c1234311fa4dbedf0cd0217ae Mon Sep 17 00:00:00 2001 From: mnewlive Date: Fri, 20 Aug 2021 15:50:32 +0300 Subject: [PATCH 074/110] RM default value for enum, modified createNotEncryptedAuthResponseV2 --- .../Extensions/SEEncryptedDataExtensions.swift | 2 +- .../Data Sources/AuthorizationsDataSourceSpec.swift | 8 +++++--- Example/Tests/Spec Helpers/SpecUtils.swift | 13 ++----------- .../Classes/Helpers/AuthorizationStatus.swift | 2 +- 4 files changed, 9 insertions(+), 16 deletions(-) diff --git a/Example/Authenticator/Utils/Extensions/SEEncryptedDataExtensions.swift b/Example/Authenticator/Utils/Extensions/SEEncryptedDataExtensions.swift index 3cbf4e66..088d657e 100644 --- a/Example/Authenticator/Utils/Extensions/SEEncryptedDataExtensions.swift +++ b/Example/Authenticator/Utils/Extensions/SEEncryptedDataExtensions.swift @@ -37,7 +37,7 @@ extension SEBaseEncryptedAuthorizationData { guard let v2Response = self as? SEEncryptedAuthorizationData, let connectionId = connectionId else { return nil } - if v2Response.status.isFinal || v2Response.status.isClosed { + if v2Response.status.isFinal { return SEAuthorizationDataV2( id: v2Response.id, connectionId: connectionId, diff --git a/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift b/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift index bcbc2fbf..11f61568 100644 --- a/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift +++ b/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift @@ -23,6 +23,7 @@ import Quick import Nimble @testable import SEAuthenticator +@testable import SEAuthenticatorCore class AuthorizationsDataSourceSpec: BaseSpec { override func spec() { @@ -230,7 +231,8 @@ class AuthorizationsDataSourceSpec: BaseSpec { with: authMessage, authorizationId: 909, connectionId: Int(connection.id)!, - guid: connection.guid + guid: connection.guid, + status: AuthorizationStatus.denied ) _ = dataSource.update(with: [finalStatusAuthorization]) @@ -264,14 +266,14 @@ class AuthorizationsDataSourceSpec: BaseSpec { _ = dataSource.update(with: [decryptedData]) - expect(dataSource.rows).to(equal(0)) + expect(dataSource.rows).to(equal(1)) let closedStatusAuthorization = SpecUtils.createNotEncryptedAuthResponseV2( with: authMessage, authorizationId: 909, connectionId: Int(connection.id)!, guid: connection.guid, - status: "closed" + status: AuthorizationStatus.closed ) _ = dataSource.update(with: [closedStatusAuthorization]) diff --git a/Example/Tests/Spec Helpers/SpecUtils.swift b/Example/Tests/Spec Helpers/SpecUtils.swift index bb1e4c0f..e823f6ad 100644 --- a/Example/Tests/Spec Helpers/SpecUtils.swift +++ b/Example/Tests/Spec Helpers/SpecUtils.swift @@ -87,18 +87,9 @@ struct SpecUtils { authorizationId: Int, connectionId: Int, guid: GUID, - status: String = "denied" + status: AuthorizationStatus ) -> SEAuthorizationDataV2 { - let dict: [String: Any] = [ - "data": "", - "key": "", - "iv": "", - "id": authorizationId, - "connection_id": connectionId, - "status": status - ] - - return SEEncryptedAuthorizationData(dict)!.decryptedAuthorizationDataV2! + return SEAuthorizationDataV2(id: "\(authorizationId)", connectionId: "\(connectionId)", status: status) } diff --git a/SaltedgeAuthenticatorCore/Classes/Helpers/AuthorizationStatus.swift b/SaltedgeAuthenticatorCore/Classes/Helpers/AuthorizationStatus.swift index 82f40d52..d6a630b3 100644 --- a/SaltedgeAuthenticatorCore/Classes/Helpers/AuthorizationStatus.swift +++ b/SaltedgeAuthenticatorCore/Classes/Helpers/AuthorizationStatus.swift @@ -32,7 +32,7 @@ public enum AuthorizationStatus: String { case unavailable case confirmProcessing = "confirm_processing" case denyProcessing = "deny_processing" - case closed = "closed" + case closed public var isFinal: Bool { return self == .confirmed From 44ea47f589a1651e29b1c307f2121fa07d6a1df3 Mon Sep 17 00:00:00 2001 From: mnewlive Date: Fri, 20 Aug 2021 15:51:40 +0300 Subject: [PATCH 075/110] RM space --- .../Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift b/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift index 11f61568..492dac4a 100644 --- a/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift +++ b/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift @@ -243,7 +243,6 @@ class AuthorizationsDataSourceSpec: BaseSpec { } } - describe("toAuthorizationViewModels with closed state") { it("when recieved authorization with closed state we should skip it") { expect(dataSource.rows).to(equal(2)) From 12c7bfe2be651eb08212417f84418bfaf5035483 Mon Sep 17 00:00:00 2001 From: mnewlive Date: Thu, 26 Aug 2021 11:28:59 +0300 Subject: [PATCH 076/110] Implement Codable solution --- .../Models/Database/Connection.swift | 55 ++++++++- .../API/Models/SEBaseActionResponse.swift | 2 +- .../Classes/API/Networking/Networking.swift | 113 ++++++++++++++++-- .../Classes/API/SEEncryptedData.swift | 33 ++--- .../Classes/Helpers/AuthorizationStatus.swift | 2 +- .../Classes/Helpers/DateUtils.swift | 24 ++++ .../Classes/Helpers/TypeAliases.swift | 6 + .../API/Managers/SEActionManager.swift | 8 +- .../API/Managers/SEAuthorizationManager.swift | 32 ++--- .../API/Managers/SEConnectionManager.swift | 16 +-- .../API/Managers/SEConsentManager.swift | 16 +-- .../API/Managers/SEProviderManager.swift | 8 +- .../SEConfirmAuthorizationResponse.swift | 26 ++-- .../SECreateConnectionResponse.swift | 28 +++-- .../Models/Responses/SEProviderResponse.swift | 50 ++++---- .../SERevokeConnectionResponse.swift | 22 ++-- .../Responses/SERevokeConsentResponse.swift | 26 ++-- .../Responses/SESubmitActionResponse.swift | 33 ++--- .../API/Managers/SEActionManagerV2.swift | 8 +- .../Managers/SEAuthorizationManagerV2.swift | 32 ++--- .../API/Managers/SEConnectionManagerV2.swift | 16 +-- .../API/Managers/SEConsentManagerV2.swift | 16 +-- .../API/Managers/SEProviderManagerV2.swift | 8 +- .../Responses/AuthorizationResponses.swift | 31 ++--- .../SEConfirmAuthorizationResponseV2.swift | 27 +++-- .../SECreateConnectionResponse.swift | 21 ++-- .../API/Responses/SEProviderResponseV2.swift | 50 ++++---- .../SERevokeConnectionResponse.swift | 22 ++-- .../Responses/SERevokeConsentResponseV2.swift | 22 ++-- .../Responses/SESubmitActionResponseV2.swift | 24 ++-- .../API/SEEncryptedAuthorizationData.swift | 39 +++--- 31 files changed, 514 insertions(+), 302 deletions(-) diff --git a/Example/Authenticator/Models/Database/Connection.swift b/Example/Authenticator/Models/Database/Connection.swift index 474840d6..4c6bd280 100644 --- a/Example/Authenticator/Models/Database/Connection.swift +++ b/Example/Authenticator/Models/Database/Connection.swift @@ -28,7 +28,7 @@ enum ConnectionStatus: String { case inactive } -@objcMembers final class Connection: Object { +@objcMembers final class Connection: Object, Decodable { dynamic var id: String = "" dynamic var guid: String = UUID().uuidString dynamic var name: String = "" @@ -38,16 +38,67 @@ enum ConnectionStatus: String { dynamic var accessToken: String = "" dynamic var status: String = ConnectionStatus.inactive.rawValue dynamic var supportEmail: String = "" - dynamic let geolocationRequired = RealmOptional() + dynamic var geolocationRequired = RealmOptional() dynamic var createdAt: Date = Date() dynamic var updatedAt: Date = Date() dynamic var providerId: String? dynamic var publicKey: String = "" dynamic var apiVersion: String = "1" + private var numeric = RealmOptional() + override static func primaryKey() -> String? { return #keyPath(Connection.guid) } + + enum CodingKeys: String, CodingKey { + case id + case guid + case name + case code + case baseUrlString = "connect_url" + case logoUrlString = "provier_logo_url" + case accessToken = "access_token" + case status + case supportEmail = "support_email" + case geolocationRequired = "geolocation_required" + case createdAt = "created_at" + case updatedAt = "updated_at" + case providerId = "provider_id" + case publicKey = "public_key" + case apiVersion = "api_version" + } + + required init(from decoder: Decoder) throws { +// let decoder = JSONDecoder() +// decoder.keyDecodingStrategy = .convertFromSnakeCase +// автоматически конвертировать при создании, при парсинге результата запроса + let container = try decoder.container(keyedBy: CodingKeys.self) + name = try container.decode(String.self, forKey: .name) + code = try container.decode(String.self, forKey: .code) + baseUrlString = try container.decode(String.self, forKey: .baseUrlString) + logoUrlString = try container.decode(String.self, forKey: .logoUrlString) + accessToken = try container.decode(String.self, forKey: .accessToken) + status = try container.decode(String.self, forKey: .status) + supportEmail = try container.decode(String.self, forKey: .supportEmail) + let boolResult = try decoder.singleValueContainer() + if boolResult.decodeNil() == false { + let value = try boolResult.decode(Bool.self) + geolocationRequired = RealmOptional(value) + } + providerId = try container.decode(String.self, forKey: .providerId) + publicKey = try container.decode(String.self, forKey: .publicKey) + apiVersion = try container.decode(String.self, forKey: .apiVersion) + id = try container.decode(String.self, forKey: .id) + guid = try container.decode(String.self, forKey: .guid) + createdAt = try container.decode(String.self, forKey: .createdAt).iso8601date ?? Date() + updatedAt = try container.decode(String.self, forKey: .updatedAt).iso8601date ?? Date() + super.init() + } + + required init() { + super.init() + } } extension Connection { diff --git a/SaltedgeAuthenticatorCore/Classes/API/Models/SEBaseActionResponse.swift b/SaltedgeAuthenticatorCore/Classes/API/Models/SEBaseActionResponse.swift index cc445f35..022b2b9c 100644 --- a/SaltedgeAuthenticatorCore/Classes/API/Models/SEBaseActionResponse.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/Models/SEBaseActionResponse.swift @@ -22,7 +22,7 @@ import Foundation -public protocol SEBaseActionResponse: SerializableResponse { +public protocol SEBaseActionResponse: Decodable { var authorizationId: String? { get } var connectionId: String? { get } } diff --git a/SaltedgeAuthenticatorCore/Classes/API/Networking/Networking.swift b/SaltedgeAuthenticatorCore/Classes/API/Networking/Networking.swift index 1d8c1075..efa18d23 100644 --- a/SaltedgeAuthenticatorCore/Classes/API/Networking/Networking.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/Networking/Networking.swift @@ -22,38 +22,43 @@ import Foundation -extension Networking { - static func execute(_ request: Routable, - success: @escaping RequestSuccessBlock, - failure: @escaping FailureBlock) { - let task = URLSessionManager.shared.dataTask(with: request.asURLRequest()) { data, response, error in +struct SimpleHTTPService { + static func makeRequest( + _ request: Routable, + completion: RequestSuccessClosure? = nil, + failure: SimpleFailureClosure? = nil + ) { + + let urlRequest = request.asURLRequest() + + let task = URLSessionManager.shared.dataTask(with: urlRequest) { data, response, error in if let error = error { - DispatchQueue.main.async { failure(error.localizedDescription) } + DispatchQueue.main.async { failure?(error.localizedDescription) } } else { guard let response = response as? HTTPURLResponse else { - DispatchQueue.main.async { failure("Something went wrong") } + DispatchQueue.main.async { failure?("Something went wrong") } return } if (200...299).contains(response.statusCode) { guard let jsonData = deserializedDictionary(from: data) else { - return DispatchQueue.main.async { failure("Something went wrong") } + return DispatchQueue.main.async { failure?("Something went wrong") } } - DispatchQueue.main.async { success(jsonData) } + DispatchQueue.main.async { completion?(jsonData) } } else { guard let jsonData = deserializedDictionary(from: data) else { return DispatchQueue.main.async { - failure("Request not successful. HTTP status code: \(response.statusCode), \(response.description)") + failure?("Request not successful. HTTP status code: \(response.statusCode), \(response.description)") } } if jsonData[SENetKeys.errorMessage] as? String != nil, let errorClass = jsonData[SENetKeys.errorClass] as? String { - DispatchQueue.main.async { failure(errorClass) } + DispatchQueue.main.async { failure?(errorClass) } } else { DispatchQueue.main.async { - failure("Request not successful. HTTP status code: \(response.statusCode), \(response.description)") + failure?("Request not successful. HTTP status code: \(response.statusCode), \(response.description)") } } } @@ -73,3 +78,87 @@ extension Networking { return jsonData } } + + +public struct HTTPService { + public static func makeRequest( + _ request: Routable, + completion: SEHTTPResponse, + failure: @escaping SimpleFailureClosure + ) { + makeRequest(request.asURLRequest(), completion: completion, failure: failure) + } + + private static func makeRequest( + _ request: URLRequest, + completion: SEHTTPResponse, + failure: @escaping SimpleFailureClosure + ) { +// NOTE: Uncomment this to debug request +// let urlString = request.url?.absoluteString ?? "" +// print("🚀 Running request: \(request.httpMethod ?? "") - \(urlString)") + + _ = URLSessionManager.shared.dataTask(with: request) { data, _, error in + let decoder = JSONDecoder() + decoder.dateDecodingStrategyFormatters = [DateUtils.dateFormatter, DateUtils.ymdDateFormatter] + + let (data, error) = handleResponse(from: data, error: error, decoder: decoder) + + guard let jsonData = data, error == nil else { + DispatchQueue.main.async { failure(error!.localizedDescription) } + return + } + +// NOTE: Uncomment this to debug response data +// print("Response: ", String(data: jsonData, encoding: .utf8)) + + do { + let model = try decoder.decode(T.self, from: jsonData) + DispatchQueue.main.async { completion?(model) } + } catch { + DispatchQueue.main.async { failure(error.localizedDescription) } + } + } + } + + private static func handleResponse(from data: Data?, error: Error?, decoder: JSONDecoder) -> (Data?, Error?) { + if let error = error { return (nil, error) } + + guard let jsonData = data else { + // -1017 -- The connection cannot parse the server’s response. + let error = NSError( + domain: "", + code: -1017, + userInfo: [NSLocalizedDescriptionKey: "Data was not retrieved from request"] + ) as Error + return (nil, error) + } + + return (jsonData, nil) + } +} + +private extension JSONDecoder { + var dateDecodingStrategyFormatters: [DateFormatter]? { + get { + return nil + } + set { + guard let formatters = newValue else { return } + + self.dateDecodingStrategy = .custom { decoder in + let container = try decoder.singleValueContainer() + let dateString = try container.decode(String.self) + + for formatter in formatters { + if let date = formatter.date(from: dateString) { + return date + } + } + + throw DecodingError.dataCorruptedError(in: container, debugDescription: "Cannot decode date string \(dateString)") + } + } + } +} + diff --git a/SaltedgeAuthenticatorCore/Classes/API/SEEncryptedData.swift b/SaltedgeAuthenticatorCore/Classes/API/SEEncryptedData.swift index 85ac8735..c9a14c6f 100644 --- a/SaltedgeAuthenticatorCore/Classes/API/SEEncryptedData.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/SEEncryptedData.swift @@ -22,7 +22,7 @@ import Foundation -public struct SEEncryptedData: SEBaseEncryptedAuthorizationData, SerializableResponse, Equatable { +public struct SEEncryptedData: SEBaseEncryptedAuthorizationData, Decodable, Equatable { private let defaultAlgorithm = "AES-256-CBC" public let data: String @@ -66,27 +66,28 @@ public struct SEEncryptedData: SEBaseEncryptedAuthorizationData, SerializableRes } } -public struct SEEncryptedDataResponse: SerializableResponse { +public struct SEEncryptedDataResponse: Decodable { public var data: SEEncryptedData - public init?(_ value: Any) { - if let response = (value as AnyObject)[SENetKeys.data] as? [String: Any], - let data = SEEncryptedData(response) { - self.data = data - } else { - return nil - } + enum CodingKeys: String, CodingKey { + case data + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + data = try container.decode(SEEncryptedData.self, forKey: .data) } } -public struct SEEncryptedListResponse: SerializableResponse { +public struct SEEncryptedListResponse: Decodable { public var data: [SEEncryptedData] = [] - public init?(_ value: Any) { - if let responses = (value as AnyObject)[SENetKeys.data] as? [[String: Any]] { - self.data = responses.compactMap { SEEncryptedData($0) } - } else { - return nil - } + enum CodingKeys: String, CodingKey { + case data + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + data = try container.decode([SEEncryptedData].self, forKey: .data) } } diff --git a/SaltedgeAuthenticatorCore/Classes/Helpers/AuthorizationStatus.swift b/SaltedgeAuthenticatorCore/Classes/Helpers/AuthorizationStatus.swift index 53f44794..8a98191d 100644 --- a/SaltedgeAuthenticatorCore/Classes/Helpers/AuthorizationStatus.swift +++ b/SaltedgeAuthenticatorCore/Classes/Helpers/AuthorizationStatus.swift @@ -22,7 +22,7 @@ import Foundation -public enum AuthorizationStatus: String { +public enum AuthorizationStatus: String, Decodable { case pending case processing case confirmed diff --git a/SaltedgeAuthenticatorCore/Classes/Helpers/DateUtils.swift b/SaltedgeAuthenticatorCore/Classes/Helpers/DateUtils.swift index 543583a3..c61280ba 100644 --- a/SaltedgeAuthenticatorCore/Classes/Helpers/DateUtils.swift +++ b/SaltedgeAuthenticatorCore/Classes/Helpers/DateUtils.swift @@ -27,6 +27,14 @@ public struct DateUtils { return shared.iso8601dateFormatter } + static var dateFormatter: DateFormatter { + return shared.dateFormatter + } + + static var ymdDateFormatter: DateFormatter { + return shared.ymdDateFormatter + } + private static let shared = DateUtils() private var iso8601dateFormatter: ISO8601DateFormatter = { @@ -35,6 +43,22 @@ public struct DateUtils { formatter.formatOptions = [.withFullDate, .withTime, .withDashSeparatorInDate, .withColonSeparatorInTime] return formatter }() + + fileprivate var ymdDateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy'-'MM'-'dd" + formatter.timeZone = TimeZone.utc + return formatter + }() + + fileprivate var dateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.calendar = Calendar(identifier: .iso8601) + formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX" + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.timeZone = TimeZone.utc + return formatter + }() } public extension String { diff --git a/SaltedgeAuthenticatorCore/Classes/Helpers/TypeAliases.swift b/SaltedgeAuthenticatorCore/Classes/Helpers/TypeAliases.swift index 70ecc0a3..57df6c75 100644 --- a/SaltedgeAuthenticatorCore/Classes/Helpers/TypeAliases.swift +++ b/SaltedgeAuthenticatorCore/Classes/Helpers/TypeAliases.swift @@ -34,3 +34,9 @@ public typealias ID = String public typealias PushToken = String public typealias ConnectQuery = String public typealias ApplicationLanguage = String + +public typealias SuccessClosure = () -> () +public typealias SimpleFailureClosure = (String) -> () +public typealias FullFailureClosure = ([String: Any]) -> () +public typealias RequestSuccessClosure = ([String: Any]?) -> () +public typealias SEHTTPResponse = ((T) -> Void)? diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEActionManager.swift b/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEActionManager.swift index 733b9a06..dad1dead 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEActionManager.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEActionManager.swift @@ -26,12 +26,12 @@ import SEAuthenticatorCore public struct SEActionManager { public static func submitAction( data: SEActionRequestData, - onSuccess success: @escaping HTTPServiceSuccessClosure, + onSuccess success: SEHTTPResponse, onFailure failure: @escaping FailureBlock ) { - HTTPService.execute( - request: SEActionRouter.submit(data), - success: success, + HTTPService.makeRequest( + SEActionRouter.submit(data), + completion: success, failure: failure ) } diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEAuthorizationManager.swift b/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEAuthorizationManager.swift index 4c582ca6..58479ae5 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEAuthorizationManager.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEAuthorizationManager.swift @@ -26,48 +26,48 @@ import SEAuthenticatorCore public struct SEAuthorizationManager { public static func getEncryptedAuthorizations( data: SEBaseAuthenticatedRequestData, - onSuccess success: @escaping HTTPServiceSuccessClosure, + onSuccess success: SEHTTPResponse, onFailure failure: @escaping FailureBlock ) { - HTTPService.execute( - request: SEAuthorizationRouter.list(data), - success: success, + HTTPService.makeRequest( + SEAuthorizationRouter.list(data), + completion: success, failure: failure ) } public static func getEncryptedAuthorization( data: SEBaseAuthenticatedWithIdRequestData, - onSuccess success: @escaping HTTPServiceSuccessClosure, + onSuccess success: SEHTTPResponse, onFailure failure: @escaping FailureBlock ) { - HTTPService.execute( - request: SEAuthorizationRouter.getAuthorization(data), - success: success, + HTTPService.makeRequest( + SEAuthorizationRouter.getAuthorization(data), + completion: success, failure: failure ) } public static func confirmAuthorization( data: SEConfirmAuthorizationRequestData, - onSuccess success: @escaping HTTPServiceSuccessClosure, + onSuccess success: SEHTTPResponse, onFailure failure: @escaping FailureBlock ) { - HTTPService.execute( - request: SEAuthorizationRouter.confirm(data), - success: success, + HTTPService.makeRequest( + SEAuthorizationRouter.confirm(data), + completion: success, failure: failure ) } public static func denyAuthorization( data: SEConfirmAuthorizationRequestData, - onSuccess success: @escaping HTTPServiceSuccessClosure, + onSuccess success: SEHTTPResponse, onFailure failure: @escaping FailureBlock ) { - HTTPService.execute( - request: SEAuthorizationRouter.deny(data), - success: success, + HTTPService.makeRequest( + SEAuthorizationRouter.deny(data), + completion: success, failure: failure ) } diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEConnectionManager.swift b/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEConnectionManager.swift index 2f0a9ac0..48b5666a 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEConnectionManager.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEConnectionManager.swift @@ -30,24 +30,24 @@ public struct SEConnectionManager { pushToken: PushToken, connectQuery: ConnectQuery? = nil, appLanguage: ApplicationLanguage, - onSuccess success: @escaping HTTPServiceSuccessClosure, + onSuccess success: SEHTTPResponse, onFailure failure: @escaping FailureBlock ) { - HTTPService.execute( - request: SEConnectionRouter.createConnection(url, data, pushToken, connectQuery, appLanguage), - success: success, + HTTPService.makeRequest( + SEConnectionRouter.createConnection(url, data, pushToken, connectQuery, appLanguage), + completion: success, failure: failure ) } public static func revokeConnection( data: SEBaseAuthenticatedWithIdRequestData, - onSuccess success: @escaping HTTPServiceSuccessClosure, + onSuccess success: SEHTTPResponse, onFailure failure: @escaping FailureBlock ) { - HTTPService.execute( - request: SEConnectionRouter.revoke(data), - success: success, + HTTPService.makeRequest( + SEConnectionRouter.revoke(data), + completion: success, failure: failure ) } diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEConsentManager.swift b/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEConsentManager.swift index bbdae354..74226059 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEConsentManager.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEConsentManager.swift @@ -26,24 +26,24 @@ import SEAuthenticatorCore public struct SEConsentManager { public static func getEncryptedConsents( data: SEBaseAuthenticatedRequestData, - onSuccess success: @escaping HTTPServiceSuccessClosure, + onSuccess success: SEHTTPResponse, onFailure failure: @escaping FailureBlock ) { - HTTPService.execute( - request: SEConsentRouter.list(data), - success: success, + HTTPService.makeRequest( + SEConsentRouter.list(data), + completion: success, failure: failure ) } public static func revokeConsent( data: SEBaseAuthenticatedWithIdRequestData, - onSuccess success: @escaping HTTPServiceSuccessClosure, + onSuccess success: SEHTTPResponse, onFailure failure: @escaping FailureBlock ) { - HTTPService.execute( - request: SEConsentRouter.revoke(data), - success: success, + HTTPService.makeRequest( + SEConsentRouter.revoke(data), + completion: success, failure: failure ) } diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEProviderManager.swift b/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEProviderManager.swift index 59578933..bc3fe9e8 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEProviderManager.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEProviderManager.swift @@ -26,12 +26,12 @@ import SEAuthenticatorCore public struct SEProviderManager { public static func fetchProviderData( url: URL, - onSuccess success: @escaping HTTPServiceSuccessClosure, + onSuccess success: SEHTTPResponse, onFailure failure: @escaping FailureBlock ) { - HTTPService.execute( - request: SEProviderRouter.fetchData(url), - success: success, + HTTPService.makeRequest( + SEProviderRouter.fetchData(url), + completion: success, failure: failure ) } diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SEConfirmAuthorizationResponse.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SEConfirmAuthorizationResponse.swift index 4b6596a9..25b0e4e6 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SEConfirmAuthorizationResponse.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SEConfirmAuthorizationResponse.swift @@ -23,19 +23,23 @@ import Foundation import SEAuthenticatorCore -public struct SEConfirmAuthorizationResponse: SerializableResponse { +public struct SEConfirmAuthorizationResponse: Decodable { public let id: String public let success: Bool - public init?(_ value: Any) { - if let dict = value as? [String: Any], - let data = dict[SENetKeys.data] as? [String: Any], - let success = data[SENetKeys.success] as? Bool, - let id = data[SENetKeys.id] as? String { - self.id = id - self.success = success - } else { - return nil - } + enum CodingKeys: String, CodingKey { + case data + } + + enum DataCodingKeys: String, CodingKey { + case id + case success + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let dataContainer = try container.nestedContainer(keyedBy: DataCodingKeys.self, forKey: .data) + id = try dataContainer.decode(String.self, forKey: .id) + success = try dataContainer.decode(Bool.self, forKey: .success) } } diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SECreateConnectionResponse.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SECreateConnectionResponse.swift index 8e49fd59..4b71b75a 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SECreateConnectionResponse.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SECreateConnectionResponse.swift @@ -23,20 +23,26 @@ import Foundation import SEAuthenticatorCore -public struct SECreateConnectionResponse: SerializableResponse { +public struct SECreateConnectionResponse: Decodable { public let id: String public let accessToken: String? public let connectUrl: String? // NOTE: Open in SEWebView - public init?(_ value: Any) { - if let dict = value as? [String: Any], - let data = dict[SENetKeys.data] as? [String: Any], - let id = data[SENetKeys.id] as? String { - self.id = id - self.connectUrl = data[SENetKeys.connectUrl] as? String - self.accessToken = data[SENetKeys.accessToken] as? String - } else { - return nil - } + enum CodingKeys: String, CodingKey { + case data + } + + enum DataCodingKeys: String, CodingKey { + case id + case accessToken = "access_token" + case connectUrl = "connect_url" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let dataContainer = try container.nestedContainer(keyedBy: DataCodingKeys.self, forKey: .data) + id = try dataContainer.decode(String.self, forKey: .id) + accessToken = try dataContainer.decodeIfPresent(String.self, forKey: .accessToken) + connectUrl = try dataContainer.decodeIfPresent(String.self, forKey: .connectUrl) } } diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SEProviderResponse.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SEProviderResponse.swift index 0a3619ce..6b23a74b 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SEProviderResponse.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SEProviderResponse.swift @@ -23,35 +23,39 @@ import Foundation import SEAuthenticatorCore -public struct SEProviderResponse: SerializableResponse { +public struct SEProviderResponse: Decodable { public var baseUrl: URL public let name: String public let code: String - public let version: String + public var version: String public var logoUrl: URL? public var supportEmail: String public let geolocationRequired: Bool? - public init?(_ value: Any) { - if let dict = value as? [String: Any], - let data = dict[SENetKeys.data] as? [String: Any], - let name = data[SENetKeys.name] as? String, - let code = data[SENetKeys.code] as? String, - let connectUrlString = data[SENetKeys.connectUrl] as? String, - let version = data[SENetKeys.version] as? String, - let connectUrl = URL(string: connectUrlString) { - if let logoUrlString = data[SENetKeys.logoUrl] as? String, - let logoUrl = URL(string: logoUrlString) { - self.logoUrl = logoUrl - } - geolocationRequired = data[SENetKeys.geolocationRequired] as? Bool - self.supportEmail = (data[SENetKeys.supportEmail] as? String) ?? "" - self.name = name - self.code = code - self.baseUrl = connectUrl - self.version = version - } else { - return nil - } + enum CodingKeys: String, CodingKey { + case data + } + + enum DataCodingKeys: String, CodingKey { + case name + case code + case baseUrl = "connect_url" //URL(string: connectUrlString) WHERE let connectUrlString = data[SENetKeys.connectUrl] as? String, + case logoUrl = "logo_url" //URL + case version + case supportEmail = "support_email" + case geolocationRequired = "geolocation_required" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let dataContainer = try container.nestedContainer(keyedBy: DataCodingKeys.self, forKey: .data) + name = try dataContainer.decode(String.self, forKey: .name) + code = try dataContainer.decode(String.self, forKey: .code) + baseUrl = try dataContainer.decode(URL.self, forKey: .baseUrl) + logoUrl = try dataContainer.decodeIfPresent(URL.self, forKey: .logoUrl) + version = try dataContainer.decode(String.self, forKey: .version) + supportEmail = try dataContainer.decode(String.self, forKey: .supportEmail) + version = try dataContainer.decode(String.self, forKey: .version) + geolocationRequired = try dataContainer.decodeIfPresent(Bool.self, forKey: .geolocationRequired) } } diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SERevokeConnectionResponse.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SERevokeConnectionResponse.swift index 71bec21a..a6886248 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SERevokeConnectionResponse.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SERevokeConnectionResponse.swift @@ -23,16 +23,20 @@ import Foundation import SEAuthenticatorCore -public struct SERevokeConnectionResponse: SerializableResponse { +public struct SERevokeConnectionResponse: Decodable { public let success: Bool - public init?(_ value: Any) { - if let dict = value as? [String: Any], - let data = dict[SENetKeys.data] as? [String: Any], - let success = data[SENetKeys.success] as? Bool { - self.success = success - } else { - return nil - } + enum CodingKeys: String, CodingKey { + case data + } + + enum DataCodingKeys: String, CodingKey { + case success + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let dataContainer = try container.nestedContainer(keyedBy: DataCodingKeys.self, forKey: .data) + success = try dataContainer.decode(Bool.self, forKey: .success) } } diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SERevokeConsentResponse.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SERevokeConsentResponse.swift index dd6762c9..dfbafabb 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SERevokeConsentResponse.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SERevokeConsentResponse.swift @@ -23,19 +23,23 @@ import Foundation import SEAuthenticatorCore -public struct SERevokeConsentResponse: SerializableResponse { +public struct SERevokeConsentResponse: Decodable { public let consentId: String public let success: Bool - public init?(_ value: Any) { - if let dict = value as? [String: Any], - let data = dict[SENetKeys.data] as? [String: Any], - let success = data[SENetKeys.success] as? Bool, - let consentId = data[SENetKeys.consentId] as? String { - self.consentId = consentId - self.success = success - } else { - return nil - } + enum CodingKeys: String, CodingKey { + case data + } + + enum DataCodingKeys: String, CodingKey { + case success + case consentId = "consent_id" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let dataContainer = try container.nestedContainer(keyedBy: DataCodingKeys.self, forKey: .data) + success = try dataContainer.decode(Bool.self, forKey: .success) + consentId = try dataContainer.decode(String.self, forKey: .consentId) } } diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SESubmitActionResponse.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SESubmitActionResponse.swift index ac107a17..34cac122 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SESubmitActionResponse.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SESubmitActionResponse.swift @@ -23,24 +23,27 @@ import Foundation import SEAuthenticatorCore -public struct SESubmitActionResponse: SEBaseActionResponse { +public struct SESubmitActionResponse: SEBaseActionResponse { + public let success: Bool public var authorizationId: String? public var connectionId: String? - public init?(_ value: Any) { - if let dict = value as? [String: Any], - let data = dict[SENetKeys.data] as? [String: Any], - let success = data[SENetKeys.success] as? Bool { - if let authorizationId = data[SENetKeys.authorizationId] as? String { - self.authorizationId = authorizationId - } - if let connectionId = data[SENetKeys.connectionId] as? String { - self.connectionId = connectionId - } - self.success = success - } else { - return nil - } + enum CodingKeys: String, CodingKey { + case data + } + + enum DataCodingKeys: String, CodingKey { + case success + case authorizationId = "authorization_id" + case connectionId = "connection_id" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let dataContainer = try container.nestedContainer(keyedBy: DataCodingKeys.self, forKey: .data) + success = try dataContainer.decode(Bool.self, forKey: .success) + authorizationId = try dataContainer.decodeIfPresent(String.self, forKey: .authorizationId) + connectionId = try dataContainer.decodeIfPresent(String.self, forKey: .connectionId) } } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEActionManagerV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEActionManagerV2.swift index 7a1faa99..b3985d1f 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEActionManagerV2.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEActionManagerV2.swift @@ -26,12 +26,12 @@ import SEAuthenticatorCore public struct SEActionManagerV2 { public static func submitAction( data: SEActionRequestDataV2, - onSuccess success: @escaping HTTPServiceSuccessClosure, + onSuccess success: SEHTTPResponse, onFailure failure: @escaping FailureBlock ) { - HTTPService.execute( - request: SEActionRouterV2.submit(data), - success: success, + HTTPService.makeRequest( + SEActionRouterV2.submit(data), + completion: success, failure: failure ) } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEAuthorizationManagerV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEAuthorizationManagerV2.swift index ca1116da..64797183 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEAuthorizationManagerV2.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEAuthorizationManagerV2.swift @@ -26,31 +26,31 @@ import SEAuthenticatorCore public struct SEAuthorizationManagerV2 { public static func getEncryptedAuthorizations( data: SEBaseAuthenticatedRequestData, - onSuccess success: @escaping HTTPServiceSuccessClosure, + onSuccess success: SEHTTPResponse, onFailure failure: @escaping FailureBlock ) { - HTTPService.execute( - request: SEAuthorizationRouter.list(data), - success: success, + HTTPService.makeRequest( + SEAuthorizationRouter.list(data), + completion: success, failure: failure ) } public static func getEncryptedAuthorization( data: SEBaseAuthenticatedWithIdRequestData, - onSuccess success: @escaping HTTPServiceSuccessClosure, + onSuccess success: SEHTTPResponse, onFailure failure: @escaping FailureBlock ) { - HTTPService.execute( - request: SEAuthorizationRouter.show(data), - success: success, + HTTPService.makeRequest( + SEAuthorizationRouter.show(data), + completion: success, failure: failure ) } public static func confirmAuthorization( data: SEConfirmAuthorizationRequestData, - onSuccess success: @escaping HTTPServiceSuccessClosure, + onSuccess success: SEHTTPResponse, onFailure failure: @escaping FailureBlock ) { let parameters = RequestParametersBuilder.confirmAuthorizationParams( @@ -58,16 +58,16 @@ public struct SEAuthorizationManagerV2 { exp: Date().addingTimeInterval(5.0 * 60.0).utcSeconds ) - HTTPService.execute( - request: SEAuthorizationRouter.confirm(data, parameters), - success: success, + HTTPService.makeRequest( + SEAuthorizationRouter.confirm(data, parameters), + completion: success, failure: failure ) } public static func denyAuthorization( data: SEConfirmAuthorizationRequestData, - onSuccess success: @escaping HTTPServiceSuccessClosure, + onSuccess success: SEHTTPResponse, onFailure failure: @escaping FailureBlock ) { let parameters = RequestParametersBuilder.confirmAuthorizationParams( @@ -75,9 +75,9 @@ public struct SEAuthorizationManagerV2 { exp: Date().addingTimeInterval(5.0 * 60.0).utcSeconds ) - HTTPService.execute( - request: SEAuthorizationRouter.deny(data, parameters), - success: success, + HTTPService.makeRequest( + SEAuthorizationRouter.deny(data, parameters), + completion: success, failure: failure ) } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConnectionManagerV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConnectionManagerV2.swift index 907e061e..c9ef47ec 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConnectionManagerV2.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConnectionManagerV2.swift @@ -28,24 +28,24 @@ public struct SEConnectionManagerV2 { by url: URL, params: SECreateConnectionParams, appLanguage: ApplicationLanguage, - onSuccess success: @escaping HTTPServiceSuccessClosure, + onSuccess success: SEHTTPResponse, onFailure failure: @escaping FailureBlock ) { - HTTPService.execute( - request: SEConnectionRouter.createConnection(url, params, appLanguage), - success: success, + HTTPService.makeRequest( + SEConnectionRouter.createConnection(url, params, appLanguage), + completion: success, failure: failure ) } public static func revokeConnection( data: SEBaseAuthenticatedWithIdRequestData, - onSuccess success: @escaping HTTPServiceSuccessClosure, + onSuccess success: SEHTTPResponse, onFailure failure: @escaping FailureBlock ) { - HTTPService.execute( - request: SEConnectionRouter.revoke(data), - success: success, + HTTPService.makeRequest( + SEConnectionRouter.revoke(data), + completion: success, failure: failure ) } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConsentManagerV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConsentManagerV2.swift index 349f90b0..2e163ceb 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConsentManagerV2.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConsentManagerV2.swift @@ -26,26 +26,26 @@ import SEAuthenticatorCore public struct SEConsentManagerV2 { public static func getEncryptedConsents( data: SEBaseAuthenticatedRequestData, - onSuccess success: @escaping HTTPServiceSuccessClosure, + onSuccess success: SEHTTPResponse, onFailure failure: @escaping FailureBlock ) { - HTTPService.execute( - request: SEConsentRouter.list(data), - success: success, + HTTPService.makeRequest( + SEConsentRouter.list(data), + completion: success, failure: failure ) } public static func revokeConsent( data: SEBaseAuthenticatedWithIdRequestData, - onSuccess success: @escaping HTTPServiceSuccessClosure, + onSuccess success: SEHTTPResponse, onFailure failure: @escaping FailureBlock ) { let parameters = RequestParametersBuilder.expirationTimeParameters - HTTPService.execute( - request: SEConsentRouter.revoke(data, parameters), - success: success, + HTTPService.makeRequest( + SEConsentRouter.revoke(data, parameters), + completion: success, failure: failure ) } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEProviderManagerV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEProviderManagerV2.swift index a43f7352..0f7b45d5 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEProviderManagerV2.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEProviderManagerV2.swift @@ -26,12 +26,12 @@ import SEAuthenticatorCore public struct SEProviderManagerV2 { public static func fetchProviderData( url: URL, - onSuccess success: @escaping HTTPServiceSuccessClosure, + onSuccess success: SEHTTPResponse, onFailure failure: @escaping FailureBlock ) { - HTTPService.execute( - request: SEProviderRouterV2.fetchData(url), - success: success, + HTTPService.makeRequest( + SEProviderRouterV2.fetchData(url), + completion: success, failure: failure ) } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/AuthorizationResponses.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/AuthorizationResponses.swift index c232ff9e..ef766ff3 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/AuthorizationResponses.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/AuthorizationResponses.swift @@ -23,27 +23,28 @@ import Foundation import SEAuthenticatorCore -public struct SEEncryptedAuthorizationDataResponse: SerializableResponse { +public struct SEEncryptedAuthorizationDataResponse: Decodable { public var data: SEEncryptedAuthorizationData - public init?(_ value: Any) { - if let response = (value as AnyObject)[SENetKeys.data] as? [String: Any], - let data = SEEncryptedAuthorizationData(response) { - self.data = data - } else { - return nil - } + enum CodingKeys: String, CodingKey { + case data + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + data = try container.decode(SEEncryptedAuthorizationData.self, forKey: .data) } } -public struct SEEncryptedAuthorizationsListResponse: SerializableResponse { +public struct SEEncryptedAuthorizationsListResponse: Decodable { public var data: [SEEncryptedAuthorizationData] = [] - public init?(_ value: Any) { - if let responses = (value as AnyObject)[SENetKeys.data] as? [[String: Any]] { - self.data = responses.compactMap { SEEncryptedAuthorizationData($0) } - } else { - return nil - } + enum CodingKeys: String, CodingKey { + case data + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + data = try container.decode([SEEncryptedAuthorizationData].self, forKey: .data) } } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEConfirmAuthorizationResponseV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEConfirmAuthorizationResponseV2.swift index 5c44cda9..75c4651a 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEConfirmAuthorizationResponseV2.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEConfirmAuthorizationResponseV2.swift @@ -23,20 +23,23 @@ import Foundation import SEAuthenticatorCore -public struct SEConfirmAuthorizationResponseV2: SerializableResponse { +public struct SEConfirmAuthorizationResponseV2: Decodable { public let id: String public let status: AuthorizationStatus - public init?(_ value: Any) { - if let dict = value as? [String: Any], - let data = dict[SENetKeys.data] as? [String: Any], - let statusString = data[SENetKeys.status] as? String, - let status = AuthorizationStatus(rawValue: statusString), - let id = data[SENetKeys.id] as? Int { - self.id = "\(id)" - self.status = status - } else { - return nil - } + enum CodingKeys: String, CodingKey { + case data + } + + enum DataCodingKeys: String, CodingKey { + case id + case status + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let dataContainer = try container.nestedContainer(keyedBy: DataCodingKeys.self, forKey: .data) + id = try dataContainer.decode(String.self, forKey: .id) + status = try dataContainer.decode(AuthorizationStatus.self, forKey: .status) } } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SECreateConnectionResponse.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SECreateConnectionResponse.swift index 722cf204..c80f5eb5 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SECreateConnectionResponse.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SECreateConnectionResponse.swift @@ -23,19 +23,18 @@ import Foundation import SEAuthenticatorCore -public struct SECreateConnectionResponse: SerializableResponse { +public struct SECreateConnectionResponse: Decodable { public let id: String public let authenticationUrl: String - public init?(_ value: Any) { - if let dict = value as? [String: Any], - let dataDict = dict[SENetKeys.data] as? [String: Any], - let id = dataDict[SENetKeys.connectionId] as? Int, - let authenticationUrl = dataDict[ApiConstants.authenticationUrl] as? String { - self.id = "\(id)" - self.authenticationUrl = authenticationUrl - } else { - return nil - } + enum CodingKeys: String, CodingKey { + case id = "connection_id" + case authenticationUrl = "authentication_url" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + id = try container.decode(String.self, forKey: .id) + authenticationUrl = try container.decode(String.self, forKey: .authenticationUrl) } } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponseV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponseV2.swift index cd431d85..584877f2 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponseV2.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponseV2.swift @@ -23,7 +23,7 @@ import Foundation import SEAuthenticatorCore -public struct SEProviderResponseV2: SerializableResponse { +public struct SEProviderResponseV2: Decodable { public let name: String public var baseUrl: URL public let providerId: String @@ -33,29 +33,31 @@ public struct SEProviderResponseV2: SerializableResponse { public var publicKey: String public let geolocationRequired: Bool? - public init?(_ value: Any) { - if let dict = value as? [String: Any], - let dataDict = dict[SENetKeys.data] as? [String: Any], - let id = dataDict[ApiConstants.providerId] as? Int, - let name = dataDict[ApiConstants.providerName] as? String, - let scaServiceUrlString = dataDict[ApiConstants.scaServiceUrl] as? String, - let apiVersion = dataDict[ApiConstants.apiVersion] as? String, - let publicKey = dataDict[ApiConstants.providerPublicKey] as? String, - let scaServiceUrl = URL(string: scaServiceUrlString) { - if let logoUrlString = dataDict[ApiConstants.providerLogoUrl] as? String, - let logoUrl = URL(string: logoUrlString) { - self.logoUrl = logoUrl - } - self.supportEmail = (dataDict[ApiConstants.providerSupportEmail] as? String) ?? "" - self.geolocationRequired = dataDict[SENetKeys.geolocationRequired] as? Bool + enum CodingKeys: String, CodingKey { + case data + } + + enum DataCodingKeys: String, CodingKey { + case name = "provider_name" + case baseUrl = "sca_service_url" //URL + case logoUrl = "logo_url" //URL + case apiVersion = "api_version" + case supportEmail = "support_email" + case providerId = "provider_id" + case publicKey = "provider_public_key" + case geolocationRequired = "geolocation_required" + } - self.providerId = "\(id)" - self.name = name - self.baseUrl = scaServiceUrl - self.apiVersion = apiVersion - self.publicKey = publicKey - } else { - return nil - } + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let dataContainer = try container.nestedContainer(keyedBy: DataCodingKeys.self, forKey: .data) + name = try dataContainer.decode(String.self, forKey: .name) + baseUrl = try dataContainer.decode(URL.self, forKey: .baseUrl) + logoUrl = try dataContainer.decodeIfPresent(URL.self, forKey: .logoUrl) + apiVersion = try dataContainer.decode(String.self, forKey: .apiVersion) + supportEmail = try dataContainer.decode(String.self, forKey: .supportEmail) + providerId = try dataContainer.decode(String.self, forKey: .providerId) + publicKey = try dataContainer.decode(String.self, forKey: .publicKey) + geolocationRequired = try dataContainer.decodeIfPresent(Bool.self, forKey: .geolocationRequired) } } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConnectionResponse.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConnectionResponse.swift index 32b88fb1..31a96684 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConnectionResponse.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConnectionResponse.swift @@ -23,16 +23,20 @@ import Foundation import SEAuthenticatorCore -public struct SERevokeConnectionResponse: SerializableResponse { +public struct SERevokeConnectionResponse: Decodable { public let connectionId: String - public init?(_ value: Any) { - if let dict = value as? [String: Any], - let dataDict = dict[SENetKeys.data] as? [String: Any], - let connectionId = dataDict[SENetKeys.connectionId] as? Int { - self.connectionId = "\(connectionId)" - } else { - return nil - } + enum CodingKeys: String, CodingKey { + case data + } + + enum DataCodingKeys: String, CodingKey { + case connectionId = "connection_id" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let dataContainer = try container.nestedContainer(keyedBy: DataCodingKeys.self, forKey: .data) + connectionId = try dataContainer.decode(String.self, forKey: .connectionId) } } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConsentResponseV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConsentResponseV2.swift index 05a48e74..779a33ef 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConsentResponseV2.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConsentResponseV2.swift @@ -23,16 +23,20 @@ import Foundation import SEAuthenticatorCore -public struct SERevokeConsentResponseV2: SerializableResponse { +public struct SERevokeConsentResponseV2: Decodable { public let id: String - public init?(_ value: Any) { - if let dict = value as? [String: Any], - let data = dict[SENetKeys.data] as? [String: Any], - let id = data[SENetKeys.id] as? Int { - self.id = "\(id)" - } else { - return nil - } + enum CodingKeys: String, CodingKey { + case data + } + + enum DataCodingKeys: String, CodingKey { + case id + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let dataContainer = try container.nestedContainer(keyedBy: DataCodingKeys.self, forKey: .data) + id = try dataContainer.decode(String.self, forKey: .id) } } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SESubmitActionResponseV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SESubmitActionResponseV2.swift index 4df6b1a6..5df3470b 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SESubmitActionResponseV2.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SESubmitActionResponseV2.swift @@ -27,15 +27,19 @@ public struct SESubmitActionResponseV2: SEBaseActionResponse { public var authorizationId: String? public var connectionId: String? - public init?(_ value: Any) { - if let dict = value as? [String: Any], - let data = dict[SENetKeys.data] as? [String: Any], - let authorizationId = data[SENetKeys.authorizationId] as? Int, - let connectionId = data[SENetKeys.connectionId] as? Int { - self.authorizationId = "\(authorizationId)" - self.connectionId = "\(connectionId)" - } else { - return nil - } + enum CodingKeys: String, CodingKey { + case data + } + + enum DataCodingKeys: String, CodingKey { + case authorizationId = "authorization_id" + case connectionId = "connection_id" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let dataContainer = try container.nestedContainer(keyedBy: DataCodingKeys.self, forKey: .data) + authorizationId = try dataContainer.decodeIfPresent(String.self, forKey: .authorizationId) + connectionId = try dataContainer.decodeIfPresent(String.self, forKey: .connectionId) } } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/SEEncryptedAuthorizationData.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/SEEncryptedAuthorizationData.swift index 37ef8459..46e6b4bb 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/SEEncryptedAuthorizationData.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/SEEncryptedAuthorizationData.swift @@ -23,32 +23,31 @@ import Foundation import SEAuthenticatorCore -public struct SEEncryptedAuthorizationData: SEBaseEncryptedAuthorizationData, SerializableResponse { +public struct SEEncryptedAuthorizationData: SEBaseEncryptedAuthorizationData, Decodable { public let id: String public let data: String public let key: String public let iv: String public let status: AuthorizationStatus public var connectionId: String? - public var entityId: String? + public var entityId: String? //TODO: check if we need it - public init?(_ value: Any) { - if let dict = value as? [String: Any], - let id = dict[SENetKeys.id] as? Int, - let data = dict[SENetKeys.data] as? String, - let key = dict[SENetKeys.key] as? String, - let iv = dict[SENetKeys.iv] as? String, - let statusString = dict[SENetKeys.status] as? String, - let status = AuthorizationStatus(rawValue: statusString), - let connectionId = dict[SENetKeys.connectionId] as? Int { - self.id = "\(id)" - self.data = data - self.key = key - self.iv = iv - self.status = status - self.connectionId = "\(connectionId)" - } else { - return nil - } + enum CodingKeys: String, CodingKey { + case id + case data + case key + case iv + case status + case connectionId = "connection_id" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + id = try container.decode(String.self, forKey: .id) + data = try container.decode(String.self, forKey: .data) + key = try container.decode(String.self, forKey: .key) + iv = try container.decode(String.self, forKey: .iv) + status = try container.decode(AuthorizationStatus.self, forKey: .id) + connectionId = try container.decodeIfPresent(String.self, forKey: .connectionId) } } From ee7caee7560c26e8f2538d788087e099f3c4e8d8 Mon Sep 17 00:00:00 2001 From: mnewlive Date: Thu, 26 Aug 2021 11:42:53 +0300 Subject: [PATCH 077/110] Rm redundant comments --- Example/Authenticator/Models/Database/Connection.swift | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Example/Authenticator/Models/Database/Connection.swift b/Example/Authenticator/Models/Database/Connection.swift index 4c6bd280..b2663a25 100644 --- a/Example/Authenticator/Models/Database/Connection.swift +++ b/Example/Authenticator/Models/Database/Connection.swift @@ -44,8 +44,6 @@ enum ConnectionStatus: String { dynamic var providerId: String? dynamic var publicKey: String = "" dynamic var apiVersion: String = "1" - private var numeric = RealmOptional() - override static func primaryKey() -> String? { return #keyPath(Connection.guid) @@ -70,9 +68,6 @@ enum ConnectionStatus: String { } required init(from decoder: Decoder) throws { -// let decoder = JSONDecoder() -// decoder.keyDecodingStrategy = .convertFromSnakeCase -// автоматически конвертировать при создании, при парсинге результата запроса let container = try decoder.container(keyedBy: CodingKeys.self) name = try container.decode(String.self, forKey: .name) code = try container.decode(String.self, forKey: .code) From 31332e5590fa433aaf68508d97c1d61218b60dbd Mon Sep 17 00:00:00 2001 From: mnewlive Date: Mon, 30 Aug 2021 16:21:12 +0300 Subject: [PATCH 078/110] Fixed models --- Gemfile.lock | 3 +++ .../Classes/API/Networking/Networking.swift | 26 ++++++++++++++++--- .../API/Networking/URLSessionManager.swift | 3 +++ .../Models/Responses/SEProviderResponse.swift | 4 +-- .../SEConfirmAuthorizationResponseV2.swift | 3 ++- .../SECreateConnectionResponse.swift | 10 +++++-- .../API/Responses/SEProviderResponseV2.swift | 11 +++++--- .../SERevokeConnectionResponse.swift | 3 ++- .../Responses/SERevokeConsentResponseV2.swift | 3 ++- .../Responses/SESubmitActionResponseV2.swift | 6 +++-- 10 files changed, 55 insertions(+), 17 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index af6058eb..335ffe2a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -28,6 +28,8 @@ GEM minitest (5.14.4) nanaimo (0.3.0) netrc (0.11.0) + nokogiri (1.11.3-arm64-darwin) + racc (~> 1.4) nokogiri (1.11.3-x86_64-darwin) racc (~> 1.4) pry (0.14.1) @@ -63,6 +65,7 @@ GEM rouge (~> 2.0.7) PLATFORMS + universal-darwin-20 x86_64-darwin-19 DEPENDENCIES diff --git a/SaltedgeAuthenticatorCore/Classes/API/Networking/Networking.swift b/SaltedgeAuthenticatorCore/Classes/API/Networking/Networking.swift index efa18d23..dbc530f7 100644 --- a/SaltedgeAuthenticatorCore/Classes/API/Networking/Networking.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/Networking/Networking.swift @@ -95,36 +95,47 @@ public struct HTTPService { failure: @escaping SimpleFailureClosure ) { // NOTE: Uncomment this to debug request -// let urlString = request.url?.absoluteString ?? "" -// print("🚀 Running request: \(request.httpMethod ?? "") - \(urlString)") + let urlString = request.url?.absoluteString ?? "" + print("🚀 Running request: \(request.httpMethod ?? "") - \(urlString)") - _ = URLSessionManager.shared.dataTask(with: request) { data, _, error in + let task = URLSessionManager.shared.dataTask(with: request) { data, _, error in + + print("data: \(String(describing: String(data: data!, encoding: .utf8)))") let decoder = JSONDecoder() decoder.dateDecodingStrategyFormatters = [DateUtils.dateFormatter, DateUtils.ymdDateFormatter] let (data, error) = handleResponse(from: data, error: error, decoder: decoder) guard let jsonData = data, error == nil else { + print("jsonData else") + DispatchQueue.main.async { failure(error!.localizedDescription) } return } // NOTE: Uncomment this to debug response data -// print("Response: ", String(data: jsonData, encoding: .utf8)) + print("Response: ", String(data: jsonData, encoding: .utf8)) do { + print("do") let model = try decoder.decode(T.self, from: jsonData) DispatchQueue.main.async { completion?(model) } } catch { + print("error: \(error)") + DispatchQueue.main.async { failure(error.localizedDescription) } } } + task.resume() } private static func handleResponse(from data: Data?, error: Error?, decoder: JSONDecoder) -> (Data?, Error?) { + print("handleResponse") if let error = error { return (nil, error) } guard let jsonData = data else { + print("handleResponse else ") + // -1017 -- The connection cannot parse the server’s response. let error = NSError( domain: "", @@ -134,6 +145,9 @@ public struct HTTPService { return (nil, error) } + print("handleResponse return ") + + return (jsonData, nil) } } @@ -141,9 +155,13 @@ public struct HTTPService { private extension JSONDecoder { var dateDecodingStrategyFormatters: [DateFormatter]? { get { + print("dateDecodingStrategyFormatters get") + return nil } set { + print("dateDecodingStrategyFormatters set") + guard let formatters = newValue else { return } self.dateDecodingStrategy = .custom { decoder in diff --git a/SaltedgeAuthenticatorCore/Classes/API/Networking/URLSessionManager.swift b/SaltedgeAuthenticatorCore/Classes/API/Networking/URLSessionManager.swift index 309d3ea4..e55092a1 100644 --- a/SaltedgeAuthenticatorCore/Classes/API/Networking/URLSessionManager.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/Networking/URLSessionManager.swift @@ -33,10 +33,13 @@ struct URLSessionManager { private static var sharedManager: URLSession! static func initializeManager() { + print("initializeManager") + sharedManager = createSession() } static func createSession() -> URLSession { + print("createSession") let config: URLSessionConfiguration = .default config.timeoutIntervalForRequest = 10.0 config.timeoutIntervalForResource = 30.0 diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SEProviderResponse.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SEProviderResponse.swift index 6b23a74b..f0680455 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SEProviderResponse.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SEProviderResponse.swift @@ -39,8 +39,8 @@ public struct SEProviderResponse: Decodable { enum DataCodingKeys: String, CodingKey { case name case code - case baseUrl = "connect_url" //URL(string: connectUrlString) WHERE let connectUrlString = data[SENetKeys.connectUrl] as? String, - case logoUrl = "logo_url" //URL + case baseUrl = "connect_url" + case logoUrl = "logo_url" case version case supportEmail = "support_email" case geolocationRequired = "geolocation_required" diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEConfirmAuthorizationResponseV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEConfirmAuthorizationResponseV2.swift index 75c4651a..3e21a68e 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEConfirmAuthorizationResponseV2.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEConfirmAuthorizationResponseV2.swift @@ -39,7 +39,8 @@ public struct SEConfirmAuthorizationResponseV2: Decodable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let dataContainer = try container.nestedContainer(keyedBy: DataCodingKeys.self, forKey: .data) - id = try dataContainer.decode(String.self, forKey: .id) + let authorizationId = try dataContainer.decode(Int.self, forKey: .id) + id = "\(authorizationId)" status = try dataContainer.decode(AuthorizationStatus.self, forKey: .status) } } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SECreateConnectionResponse.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SECreateConnectionResponse.swift index c80f5eb5..98b223c1 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SECreateConnectionResponse.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SECreateConnectionResponse.swift @@ -28,13 +28,19 @@ public struct SECreateConnectionResponse: Decodable { public let authenticationUrl: String enum CodingKeys: String, CodingKey { + case data + } + + enum DataCodingKeys: String, CodingKey { case id = "connection_id" case authenticationUrl = "authentication_url" } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - id = try container.decode(String.self, forKey: .id) - authenticationUrl = try container.decode(String.self, forKey: .authenticationUrl) + let dataContainer = try container.nestedContainer(keyedBy: DataCodingKeys.self, forKey: .data) + let connectionId = try dataContainer.decode(Int.self, forKey: .id) + id = "\(connectionId)" + authenticationUrl = try dataContainer.decode(String.self, forKey: .authenticationUrl) } } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponseV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponseV2.swift index 584877f2..9634f2eb 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponseV2.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponseV2.swift @@ -39,16 +39,17 @@ public struct SEProviderResponseV2: Decodable { enum DataCodingKeys: String, CodingKey { case name = "provider_name" - case baseUrl = "sca_service_url" //URL - case logoUrl = "logo_url" //URL + case baseUrl = "sca_service_url" + case logoUrl = "logo_url" case apiVersion = "api_version" - case supportEmail = "support_email" + case supportEmail = "provider_support_email" case providerId = "provider_id" case publicKey = "provider_public_key" case geolocationRequired = "geolocation_required" } public init(from decoder: Decoder) throws { + print("init decoder") let container = try decoder.container(keyedBy: CodingKeys.self) let dataContainer = try container.nestedContainer(keyedBy: DataCodingKeys.self, forKey: .data) name = try dataContainer.decode(String.self, forKey: .name) @@ -56,8 +57,10 @@ public struct SEProviderResponseV2: Decodable { logoUrl = try dataContainer.decodeIfPresent(URL.self, forKey: .logoUrl) apiVersion = try dataContainer.decode(String.self, forKey: .apiVersion) supportEmail = try dataContainer.decode(String.self, forKey: .supportEmail) - providerId = try dataContainer.decode(String.self, forKey: .providerId) + let id = try dataContainer.decode(Int.self, forKey: .providerId) + providerId = "\(id)" publicKey = try dataContainer.decode(String.self, forKey: .publicKey) geolocationRequired = try dataContainer.decodeIfPresent(Bool.self, forKey: .geolocationRequired) + print("init name \(name), baseUrl: \(baseUrl)") } } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConnectionResponse.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConnectionResponse.swift index 31a96684..cefe761b 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConnectionResponse.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConnectionResponse.swift @@ -37,6 +37,7 @@ public struct SERevokeConnectionResponse: Decodable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let dataContainer = try container.nestedContainer(keyedBy: DataCodingKeys.self, forKey: .data) - connectionId = try dataContainer.decode(String.self, forKey: .connectionId) + let id = try dataContainer.decode(Int.self, forKey: .connectionId) + connectionId = "\(id)" } } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConsentResponseV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConsentResponseV2.swift index 779a33ef..f9fc65dc 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConsentResponseV2.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConsentResponseV2.swift @@ -37,6 +37,7 @@ public struct SERevokeConsentResponseV2: Decodable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let dataContainer = try container.nestedContainer(keyedBy: DataCodingKeys.self, forKey: .data) - id = try dataContainer.decode(String.self, forKey: .id) + let consentId = try dataContainer.decode(Int.self, forKey: .id) + id = "\(consentId)" } } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SESubmitActionResponseV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SESubmitActionResponseV2.swift index 5df3470b..9fb33284 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SESubmitActionResponseV2.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SESubmitActionResponseV2.swift @@ -39,7 +39,9 @@ public struct SESubmitActionResponseV2: SEBaseActionResponse { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let dataContainer = try container.nestedContainer(keyedBy: DataCodingKeys.self, forKey: .data) - authorizationId = try dataContainer.decodeIfPresent(String.self, forKey: .authorizationId) - connectionId = try dataContainer.decodeIfPresent(String.self, forKey: .connectionId) + let authorizationV2Id = try dataContainer.decodeIfPresent(Int.self, forKey: .authorizationId) + authorizationId = "\(authorizationV2Id)" + let connectionV2Id = try dataContainer.decodeIfPresent(Int.self, forKey: .connectionId) + connectionId = "\(connectionV2Id)" } } From 8a18d46c0e608f225b63a396a94492e681ddb8c7 Mon Sep 17 00:00:00 2001 From: mnewlive Date: Wed, 1 Sep 2021 15:42:32 +0300 Subject: [PATCH 079/110] Added tests --- .../SEEncryptedDataExtensionsSpec.swift | 8 +++-- .../ConfirmAuthorizationResponseSpec.swift | 9 +++--- .../CreateConnectionResponseSpec.swift | 13 ++++---- .../Responses/ProviderResponseSpec.swift | 17 +++++----- .../RevokeConnectionResponseSpec.swift | 9 +++--- .../Responses/SubmitActionResponseSpec.swift | 11 ++++--- Example/Tests/Spec Helpers/SpecUtils.swift | 6 ++-- .../Classes/API/Networking/Networking.swift | 32 +++++++------------ .../API/Networking/URLSessionManager.swift | 3 -- .../API/SEEncryptedAuthorizationData.swift | 6 ++-- 10 files changed, 57 insertions(+), 57 deletions(-) diff --git a/Example/Tests/Extensions/SEEncryptedDataExtensionsSpec.swift b/Example/Tests/Extensions/SEEncryptedDataExtensionsSpec.swift index a8c0b070..0fd8e369 100644 --- a/Example/Tests/Extensions/SEEncryptedDataExtensionsSpec.swift +++ b/Example/Tests/Extensions/SEEncryptedDataExtensionsSpec.swift @@ -112,8 +112,9 @@ class SEEncryptedDataExtensionsSpec: BaseSpec { connectionId: connection.id, status: .pending ) - - expect(expectedData).to(equal(SEEncryptedAuthorizationData(dict)!.decryptedAuthorizationDataV2!)) + + let response = SpecDecodableModel.create(from: dict) + expect(expectedData).to(equal(response.decryptedAuthorizationDataV2!)) } } @@ -137,7 +138,8 @@ class SEEncryptedDataExtensionsSpec: BaseSpec { status: .confirmed ) - expect(expectedData).to(equal(SEEncryptedAuthorizationData(dict)!.decryptedAuthorizationDataV2!)) + let response = SpecDecodableModel.create(from: dict) + expect(expectedData).to(equal(response.decryptedAuthorizationDataV2!)) } } } diff --git a/Example/Tests/Networking/Responses/ConfirmAuthorizationResponseSpec.swift b/Example/Tests/Networking/Responses/ConfirmAuthorizationResponseSpec.swift index 7e8a7c98..6d2cd4d1 100644 --- a/Example/Tests/Networking/Responses/ConfirmAuthorizationResponseSpec.swift +++ b/Example/Tests/Networking/Responses/ConfirmAuthorizationResponseSpec.swift @@ -23,6 +23,7 @@ import Quick import Nimble @testable import SEAuthenticator +@testable import SEAuthenticatorCore class ConfirmAuthorizationResponseSpec: BaseSpec { override func spec() { @@ -30,18 +31,18 @@ class ConfirmAuthorizationResponseSpec: BaseSpec { context("when the value is a proper dictionary containing the necessary data") { it("should create correct response") { let fixture = DataFixtures.validConfirmAuthorizationData - let response = SEConfirmAuthorizationResponse(fixture) + let response = SpecDecodableModel.create(from: fixture) expect(response).toNot(beNil()) - expect(response?.success).to(beTrue()) - expect(response?.id).to(equal("1")) + expect(response.success).to(beTrue()) + expect(response.id).to(equal("1")) } } context("when the value is a malformed dictionary or is missing data") { it("should return nil and fail to initialize the object") { let fixture = DataFixtures.invalidConfirmAuthorizationData - let response = SEConfirmAuthorizationResponse(fixture) + let response = SpecDecodableModel.create(from: fixture) expect(response).to(beNil()) } diff --git a/Example/Tests/Networking/Responses/CreateConnectionResponseSpec.swift b/Example/Tests/Networking/Responses/CreateConnectionResponseSpec.swift index 1f9d7b89..6378665f 100644 --- a/Example/Tests/Networking/Responses/CreateConnectionResponseSpec.swift +++ b/Example/Tests/Networking/Responses/CreateConnectionResponseSpec.swift @@ -23,6 +23,7 @@ import Quick import Nimble @testable import SEAuthenticator +@testable import SEAuthenticatorCore class CreateConnectionResponseSpec: BaseSpec { override func spec() { @@ -30,19 +31,19 @@ class CreateConnectionResponseSpec: BaseSpec { context("when the value is a proper dictionary containing the necessary data") { it("should create correct response") { let fixture = DataFixtures.validCreateConnectionData - let expectedResponse = SECreateConnectionResponse(fixture) + let response = SpecDecodableModel.create(from: fixture) - expect(expectedResponse).toNot(beNil()) - expect(expectedResponse?.connectUrl).to(equal("connect.com")) - expect(expectedResponse?.id).to(equal("123456789")) + expect(response).toNot(beNil()) + expect(response.connectUrl).to(equal("connect.com")) + expect(response.id).to(equal("123456789")) } } context("when the value is a malformed dictionary or is missing data") { it("should return nil and fail to initialize the object") { let fixture = DataFixtures.invalidCreateConnectionData - let response = SECreateConnectionResponse(fixture) - + let response = SpecDecodableModel.create(from: fixture) + expect(response).to(beNil()) } } diff --git a/Example/Tests/Networking/Responses/ProviderResponseSpec.swift b/Example/Tests/Networking/Responses/ProviderResponseSpec.swift index a6531743..73b751be 100644 --- a/Example/Tests/Networking/Responses/ProviderResponseSpec.swift +++ b/Example/Tests/Networking/Responses/ProviderResponseSpec.swift @@ -23,6 +23,7 @@ import Quick import Nimble @testable import SEAuthenticator +@testable import SEAuthenticatorCore class ProviderResponseSpec: BaseSpec { override func spec() { @@ -30,21 +31,21 @@ class ProviderResponseSpec: BaseSpec { context("when the value is a proper dictionary containing the necessary data") { it("should create correct response") { let fixture = DataFixtures.validProviderResponse - let expectedResponse = SEProviderResponse(fixture) + let response = SpecDecodableModel.create(from: fixture) - expect(expectedResponse).toNot(beNil()) - expect(expectedResponse?.code).to(equal("demobank")) - expect(expectedResponse?.baseUrl.absoluteString).to(equal("getConnectUrl.com")) - expect(expectedResponse?.name).to(equal("Demobank")) - expect(expectedResponse?.logoUrl?.absoluteString).to(equal("https://upload.wikimedia.org/wikipedia/commons/6/6f/HP_logo_630x630.png")) - expect(expectedResponse?.version).to(equal("1")) + expect(response).toNot(beNil()) + expect(response.code).to(equal("demobank")) + expect(response.baseUrl.absoluteString).to(equal("getConnectUrl.com")) + expect(response.name).to(equal("Demobank")) + expect(response.logoUrl?.absoluteString).to(equal("https://upload.wikimedia.org/wikipedia/commons/6/6f/HP_logo_630x630.png")) + expect(response.version).to(equal("1")) } } context("when the value is a malformed dictionary or is missing data") { it("should return nil and fail to initialize the object") { let fixture = DataFixtures.invalidProviderResponse - let response = SEProviderResponse(fixture) + let response = SpecDecodableModel.create(from: fixture) expect(response).to(beNil()) } diff --git a/Example/Tests/Networking/Responses/RevokeConnectionResponseSpec.swift b/Example/Tests/Networking/Responses/RevokeConnectionResponseSpec.swift index 9f575386..7cd46387 100644 --- a/Example/Tests/Networking/Responses/RevokeConnectionResponseSpec.swift +++ b/Example/Tests/Networking/Responses/RevokeConnectionResponseSpec.swift @@ -23,6 +23,7 @@ import Quick import Nimble @testable import SEAuthenticator +@testable import SEAuthenticatorCore class RevokeConnectionResponseSpec: BaseSpec { override func spec() { @@ -30,18 +31,18 @@ class RevokeConnectionResponseSpec: BaseSpec { context("when the value is a proper dictionary containing the necessary data") { it("should create correct response") { let fixture = DataFixtures.validRevokeConnectionData - let response = SERevokeConnectionResponse(fixture) + let response = SpecDecodableModel.create(from: fixture) expect(response).toNot(beNil()) - expect(response?.success).to(beTrue()) + expect(response.success).to(beTrue()) } } context("when the value is a malformed dictionary or is missing data") { it("should return nil and fail to initialize the object") { let fixture = DataFixtures.invalidRevokeConnectionData - let response = SERevokeConnectionResponse(fixture) - + let response = SpecDecodableModel.create(from: fixture) + expect(response).to(beNil()) } } diff --git a/Example/Tests/Networking/Responses/SubmitActionResponseSpec.swift b/Example/Tests/Networking/Responses/SubmitActionResponseSpec.swift index 5f3b1887..fbba4c18 100644 --- a/Example/Tests/Networking/Responses/SubmitActionResponseSpec.swift +++ b/Example/Tests/Networking/Responses/SubmitActionResponseSpec.swift @@ -23,6 +23,7 @@ import Quick import Nimble @testable import SEAuthenticator +@testable import SEAuthenticatorCore class SubmitActionResponseSpec: BaseSpec { override func spec() { @@ -30,19 +31,19 @@ class SubmitActionResponseSpec: BaseSpec { context("when the value is a proper dictionary containing the necessary data") { it("should create correct response") { let fixture = DataFixtures.validSubmitActionData - let response = SESubmitActionResponse(fixture) + let response = SpecDecodableModel.create(from: fixture) expect(response).toNot(beNil()) - expect(response?.success).to(beTrue()) - expect(response?.authorizationId).to(equal("333")) - expect(response?.connectionId).to(equal("444")) + expect(response.success).to(beTrue()) + expect(response.authorizationId).to(equal("333")) + expect(response.connectionId).to(equal("444")) } } context("when the value is a malformed dictionary or is missing data") { it("should return nil and fail to initialize the object") { let fixture = DataFixtures.invalidSubmitActionData - let response = SESubmitActionResponse(fixture) + let response = SpecDecodableModel.create(from: fixture) expect(response).to(beNil()) } diff --git a/Example/Tests/Spec Helpers/SpecUtils.swift b/Example/Tests/Spec Helpers/SpecUtils.swift index e753625f..6d2f02cf 100644 --- a/Example/Tests/Spec Helpers/SpecUtils.swift +++ b/Example/Tests/Spec Helpers/SpecUtils.swift @@ -78,7 +78,8 @@ struct SpecUtils { "status": "pending" ] - return SEEncryptedAuthorizationData(dict)!.decryptedAuthorizationDataV2! + let response = SpecDecodableModel.create(from: dict) + return response.decryptedAuthorizationDataV2! } static func createFinalAuthResponseV2( @@ -96,7 +97,8 @@ struct SpecUtils { "status": "denied" ] - return SEEncryptedAuthorizationData(dict)!.decryptedAuthorizationDataV2! + let response = SpecDecodableModel.create(from: dict) + return response.decryptedAuthorizationDataV2! } diff --git a/SaltedgeAuthenticatorCore/Classes/API/Networking/Networking.swift b/SaltedgeAuthenticatorCore/Classes/API/Networking/Networking.swift index dbc530f7..7879f57c 100644 --- a/SaltedgeAuthenticatorCore/Classes/API/Networking/Networking.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/Networking/Networking.swift @@ -95,34 +95,27 @@ public struct HTTPService { failure: @escaping SimpleFailureClosure ) { // NOTE: Uncomment this to debug request - let urlString = request.url?.absoluteString ?? "" - print("🚀 Running request: \(request.httpMethod ?? "") - \(urlString)") +// let urlString = request.url?.absoluteString ?? "" +// print("🚀 Running request: \(request.httpMethod ?? "") - \(urlString)") let task = URLSessionManager.shared.dataTask(with: request) { data, _, error in - - print("data: \(String(describing: String(data: data!, encoding: .utf8)))") let decoder = JSONDecoder() decoder.dateDecodingStrategyFormatters = [DateUtils.dateFormatter, DateUtils.ymdDateFormatter] let (data, error) = handleResponse(from: data, error: error, decoder: decoder) guard let jsonData = data, error == nil else { - print("jsonData else") - DispatchQueue.main.async { failure(error!.localizedDescription) } return } // NOTE: Uncomment this to debug response data - print("Response: ", String(data: jsonData, encoding: .utf8)) +// print("Response: ", String(data: jsonData, encoding: .utf8)) do { - print("do") let model = try decoder.decode(T.self, from: jsonData) DispatchQueue.main.async { completion?(model) } } catch { - print("error: \(error)") - DispatchQueue.main.async { failure(error.localizedDescription) } } } @@ -130,12 +123,9 @@ public struct HTTPService { } private static func handleResponse(from data: Data?, error: Error?, decoder: JSONDecoder) -> (Data?, Error?) { - print("handleResponse") if let error = error { return (nil, error) } guard let jsonData = data else { - print("handleResponse else ") - // -1017 -- The connection cannot parse the server’s response. let error = NSError( domain: "", @@ -145,23 +135,25 @@ public struct HTTPService { return (nil, error) } - print("handleResponse return ") - - return (jsonData, nil) } } +public struct SpecDecodableModel { + public static func create(from fixture: [String: Any]) -> T { + let decoder = JSONDecoder() + decoder.dateDecodingStrategyFormatters = [DateUtils.dateFormatter, DateUtils.ymdDateFormatter] + let fixtureData = Data(fixture.jsonString!.utf8) + return try! decoder.decode(T.self, from: fixtureData) + } +} + private extension JSONDecoder { var dateDecodingStrategyFormatters: [DateFormatter]? { get { - print("dateDecodingStrategyFormatters get") - return nil } set { - print("dateDecodingStrategyFormatters set") - guard let formatters = newValue else { return } self.dateDecodingStrategy = .custom { decoder in diff --git a/SaltedgeAuthenticatorCore/Classes/API/Networking/URLSessionManager.swift b/SaltedgeAuthenticatorCore/Classes/API/Networking/URLSessionManager.swift index e55092a1..309d3ea4 100644 --- a/SaltedgeAuthenticatorCore/Classes/API/Networking/URLSessionManager.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/Networking/URLSessionManager.swift @@ -33,13 +33,10 @@ struct URLSessionManager { private static var sharedManager: URLSession! static func initializeManager() { - print("initializeManager") - sharedManager = createSession() } static func createSession() -> URLSession { - print("createSession") let config: URLSessionConfiguration = .default config.timeoutIntervalForRequest = 10.0 config.timeoutIntervalForResource = 30.0 diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/SEEncryptedAuthorizationData.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/SEEncryptedAuthorizationData.swift index 46e6b4bb..fdf03dc8 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/SEEncryptedAuthorizationData.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/SEEncryptedAuthorizationData.swift @@ -43,11 +43,13 @@ public struct SEEncryptedAuthorizationData: SEBaseEncryptedAuthorizationData, De public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - id = try container.decode(String.self, forKey: .id) + let authorizationV2Id = try container.decode(Int.self, forKey: .id) + id = "\(authorizationV2Id)" data = try container.decode(String.self, forKey: .data) key = try container.decode(String.self, forKey: .key) iv = try container.decode(String.self, forKey: .iv) status = try container.decode(AuthorizationStatus.self, forKey: .id) - connectionId = try container.decodeIfPresent(String.self, forKey: .connectionId) + let connectionV2Id = try container.decodeIfPresent(Int.self, forKey: .connectionId) + connectionId = "\(connectionV2Id)" } } From b0f68423ce7508816e7c9de3c040ba70d9e30f93 Mon Sep 17 00:00:00 2001 From: mnewlive Date: Wed, 1 Sep 2021 17:28:37 +0300 Subject: [PATCH 080/110] Fixed all tests --- .../Extensions/SEEncryptedDataExtensionsSpec.swift | 4 ++-- .../Responses/ConfirmAuthorizationResponseSpec.swift | 4 ++-- .../Responses/CreateConnectionResponseSpec.swift | 4 ++-- .../Networking/Responses/ProviderResponseSpec.swift | 10 +++++----- .../Responses/RevokeConnectionResponseSpec.swift | 2 +- .../Responses/SubmitActionResponseSpec.swift | 6 +++--- Example/Tests/Spec Helpers/SpecUtils.swift | 4 ++-- .../Classes/API/Networking/Networking.swift | 4 ++-- .../Classes/API/SEEncryptedAuthorizationData.swift | 7 ++++--- 9 files changed, 23 insertions(+), 22 deletions(-) diff --git a/Example/Tests/Extensions/SEEncryptedDataExtensionsSpec.swift b/Example/Tests/Extensions/SEEncryptedDataExtensionsSpec.swift index 0fd8e369..5b027725 100644 --- a/Example/Tests/Extensions/SEEncryptedDataExtensionsSpec.swift +++ b/Example/Tests/Extensions/SEEncryptedDataExtensionsSpec.swift @@ -114,7 +114,7 @@ class SEEncryptedDataExtensionsSpec: BaseSpec { ) let response = SpecDecodableModel.create(from: dict) - expect(expectedData).to(equal(response.decryptedAuthorizationDataV2!)) + expect(expectedData).to(equal(response?.decryptedAuthorizationDataV2!)) } } @@ -139,7 +139,7 @@ class SEEncryptedDataExtensionsSpec: BaseSpec { ) let response = SpecDecodableModel.create(from: dict) - expect(expectedData).to(equal(response.decryptedAuthorizationDataV2!)) + expect(expectedData).to(equal(response?.decryptedAuthorizationDataV2!)) } } } diff --git a/Example/Tests/Networking/Responses/ConfirmAuthorizationResponseSpec.swift b/Example/Tests/Networking/Responses/ConfirmAuthorizationResponseSpec.swift index 6d2cd4d1..24b8ee0e 100644 --- a/Example/Tests/Networking/Responses/ConfirmAuthorizationResponseSpec.swift +++ b/Example/Tests/Networking/Responses/ConfirmAuthorizationResponseSpec.swift @@ -34,8 +34,8 @@ class ConfirmAuthorizationResponseSpec: BaseSpec { let response = SpecDecodableModel.create(from: fixture) expect(response).toNot(beNil()) - expect(response.success).to(beTrue()) - expect(response.id).to(equal("1")) + expect(response?.success).to(beTrue()) + expect(response?.id).to(equal("1")) } } diff --git a/Example/Tests/Networking/Responses/CreateConnectionResponseSpec.swift b/Example/Tests/Networking/Responses/CreateConnectionResponseSpec.swift index 6378665f..5df5c71d 100644 --- a/Example/Tests/Networking/Responses/CreateConnectionResponseSpec.swift +++ b/Example/Tests/Networking/Responses/CreateConnectionResponseSpec.swift @@ -34,8 +34,8 @@ class CreateConnectionResponseSpec: BaseSpec { let response = SpecDecodableModel.create(from: fixture) expect(response).toNot(beNil()) - expect(response.connectUrl).to(equal("connect.com")) - expect(response.id).to(equal("123456789")) + expect(response?.connectUrl).to(equal("connect.com")) + expect(response?.id).to(equal("123456789")) } } diff --git a/Example/Tests/Networking/Responses/ProviderResponseSpec.swift b/Example/Tests/Networking/Responses/ProviderResponseSpec.swift index 73b751be..9e3fe3f2 100644 --- a/Example/Tests/Networking/Responses/ProviderResponseSpec.swift +++ b/Example/Tests/Networking/Responses/ProviderResponseSpec.swift @@ -34,11 +34,11 @@ class ProviderResponseSpec: BaseSpec { let response = SpecDecodableModel.create(from: fixture) expect(response).toNot(beNil()) - expect(response.code).to(equal("demobank")) - expect(response.baseUrl.absoluteString).to(equal("getConnectUrl.com")) - expect(response.name).to(equal("Demobank")) - expect(response.logoUrl?.absoluteString).to(equal("https://upload.wikimedia.org/wikipedia/commons/6/6f/HP_logo_630x630.png")) - expect(response.version).to(equal("1")) + expect(response?.code).to(equal("demobank")) + expect(response?.baseUrl.absoluteString).to(equal("getConnectUrl.com")) + expect(response?.name).to(equal("Demobank")) + expect(response?.logoUrl?.absoluteString).to(equal("https://upload.wikimedia.org/wikipedia/commons/6/6f/HP_logo_630x630.png")) + expect(response?.version).to(equal("1")) } } diff --git a/Example/Tests/Networking/Responses/RevokeConnectionResponseSpec.swift b/Example/Tests/Networking/Responses/RevokeConnectionResponseSpec.swift index 7cd46387..906ca8d7 100644 --- a/Example/Tests/Networking/Responses/RevokeConnectionResponseSpec.swift +++ b/Example/Tests/Networking/Responses/RevokeConnectionResponseSpec.swift @@ -34,7 +34,7 @@ class RevokeConnectionResponseSpec: BaseSpec { let response = SpecDecodableModel.create(from: fixture) expect(response).toNot(beNil()) - expect(response.success).to(beTrue()) + expect(response?.success).to(beTrue()) } } diff --git a/Example/Tests/Networking/Responses/SubmitActionResponseSpec.swift b/Example/Tests/Networking/Responses/SubmitActionResponseSpec.swift index fbba4c18..4953cd51 100644 --- a/Example/Tests/Networking/Responses/SubmitActionResponseSpec.swift +++ b/Example/Tests/Networking/Responses/SubmitActionResponseSpec.swift @@ -34,9 +34,9 @@ class SubmitActionResponseSpec: BaseSpec { let response = SpecDecodableModel.create(from: fixture) expect(response).toNot(beNil()) - expect(response.success).to(beTrue()) - expect(response.authorizationId).to(equal("333")) - expect(response.connectionId).to(equal("444")) + expect(response?.success).to(beTrue()) + expect(response?.authorizationId).to(equal("333")) + expect(response?.connectionId).to(equal("444")) } } diff --git a/Example/Tests/Spec Helpers/SpecUtils.swift b/Example/Tests/Spec Helpers/SpecUtils.swift index 6d2f02cf..e7a7003b 100644 --- a/Example/Tests/Spec Helpers/SpecUtils.swift +++ b/Example/Tests/Spec Helpers/SpecUtils.swift @@ -79,7 +79,7 @@ struct SpecUtils { ] let response = SpecDecodableModel.create(from: dict) - return response.decryptedAuthorizationDataV2! + return (response?.decryptedAuthorizationDataV2!)! } static func createFinalAuthResponseV2( @@ -98,7 +98,7 @@ struct SpecUtils { ] let response = SpecDecodableModel.create(from: dict) - return response.decryptedAuthorizationDataV2! + return (response?.decryptedAuthorizationDataV2!)! } diff --git a/SaltedgeAuthenticatorCore/Classes/API/Networking/Networking.swift b/SaltedgeAuthenticatorCore/Classes/API/Networking/Networking.swift index 7879f57c..2f275a37 100644 --- a/SaltedgeAuthenticatorCore/Classes/API/Networking/Networking.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/Networking/Networking.swift @@ -140,11 +140,11 @@ public struct HTTPService { } public struct SpecDecodableModel { - public static func create(from fixture: [String: Any]) -> T { + public static func create(from fixture: [String: Any]) -> T? { let decoder = JSONDecoder() decoder.dateDecodingStrategyFormatters = [DateUtils.dateFormatter, DateUtils.ymdDateFormatter] let fixtureData = Data(fixture.jsonString!.utf8) - return try! decoder.decode(T.self, from: fixtureData) + return try? decoder.decode(T.self, from: fixtureData) } } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/SEEncryptedAuthorizationData.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/SEEncryptedAuthorizationData.swift index fdf03dc8..bbd4c98d 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/SEEncryptedAuthorizationData.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/SEEncryptedAuthorizationData.swift @@ -48,8 +48,9 @@ public struct SEEncryptedAuthorizationData: SEBaseEncryptedAuthorizationData, De data = try container.decode(String.self, forKey: .data) key = try container.decode(String.self, forKey: .key) iv = try container.decode(String.self, forKey: .iv) - status = try container.decode(AuthorizationStatus.self, forKey: .id) - let connectionV2Id = try container.decodeIfPresent(Int.self, forKey: .connectionId) - connectionId = "\(connectionV2Id)" + status = try container.decode(AuthorizationStatus.self, forKey: .status) + if let connectionV2Id = try container.decodeIfPresent(Int.self, forKey: .connectionId) { + connectionId = "\(connectionV2Id)" + } } } From 779f74c152f28e230a841e3ea7ddbaee513953a2 Mon Sep 17 00:00:00 2001 From: mnewlive Date: Fri, 3 Sep 2021 17:36:22 +0300 Subject: [PATCH 081/110] Resolved some discussions --- Example/Tests/Spec Helpers/SpecUtils.swift | 33 +++++++++ .../Classes/API/Networking/Networking.swift | 67 ------------------- .../Classes/Helpers/DateUtils.swift | 4 +- .../Classes/Helpers/TypeAliases.swift | 1 - .../Responses/SESubmitActionResponse.swift | 3 +- .../API/Responses/SEProviderResponseV2.swift | 2 - .../API/SEEncryptedAuthorizationData.swift | 2 +- 7 files changed, 37 insertions(+), 75 deletions(-) diff --git a/Example/Tests/Spec Helpers/SpecUtils.swift b/Example/Tests/Spec Helpers/SpecUtils.swift index b4dae86a..50a7b49a 100644 --- a/Example/Tests/Spec Helpers/SpecUtils.swift +++ b/Example/Tests/Spec Helpers/SpecUtils.swift @@ -116,3 +116,36 @@ struct SpecUtils { "-----END PUBLIC KEY-----\n" } } + +public struct SpecDecodableModel { + public static func create(from fixture: [String: Any]) -> T? { + let decoder = JSONDecoder() + decoder.dateDecodingStrategyFormatters = [DateUtils.dateFormatter, DateUtils.ymdDateFormatter] + let fixtureData = Data(fixture.jsonString!.utf8) + return try? decoder.decode(T.self, from: fixtureData) + } +} + +private extension JSONDecoder { + var dateDecodingStrategyFormatters: [DateFormatter]? { + get { + return nil + } + set { + guard let formatters = newValue else { return } + + self.dateDecodingStrategy = .custom { decoder in + let container = try decoder.singleValueContainer() + let dateString = try container.decode(String.self) + + for formatter in formatters { + if let date = formatter.date(from: dateString) { + return date + } + } + + throw DecodingError.dataCorruptedError(in: container, debugDescription: "Cannot decode date string \(dateString)") + } + } + } +} diff --git a/SaltedgeAuthenticatorCore/Classes/API/Networking/Networking.swift b/SaltedgeAuthenticatorCore/Classes/API/Networking/Networking.swift index 2f275a37..9b6bc257 100644 --- a/SaltedgeAuthenticatorCore/Classes/API/Networking/Networking.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/Networking/Networking.swift @@ -22,64 +22,6 @@ import Foundation -struct SimpleHTTPService { - static func makeRequest( - _ request: Routable, - completion: RequestSuccessClosure? = nil, - failure: SimpleFailureClosure? = nil - ) { - - let urlRequest = request.asURLRequest() - - let task = URLSessionManager.shared.dataTask(with: urlRequest) { data, response, error in - if let error = error { - DispatchQueue.main.async { failure?(error.localizedDescription) } - } else { - guard let response = response as? HTTPURLResponse else { - DispatchQueue.main.async { failure?("Something went wrong") } - return - } - - if (200...299).contains(response.statusCode) { - guard let jsonData = deserializedDictionary(from: data) else { - return DispatchQueue.main.async { failure?("Something went wrong") } - } - - DispatchQueue.main.async { completion?(jsonData) } - } else { - guard let jsonData = deserializedDictionary(from: data) else { - return DispatchQueue.main.async { - failure?("Request not successful. HTTP status code: \(response.statusCode), \(response.description)") - } - } - - if jsonData[SENetKeys.errorMessage] as? String != nil, - let errorClass = jsonData[SENetKeys.errorClass] as? String { - DispatchQueue.main.async { failure?(errorClass) } - } else { - DispatchQueue.main.async { - failure?("Request not successful. HTTP status code: \(response.statusCode), \(response.description)") - } - } - } - } - } - - task.resume() - } - - private static func deserializedDictionary(from data: Data?) -> [String: Any]? { - guard let requestData = data, - let jsonData = try? JSONSerialization.jsonObject( - with: requestData, - options: [] - ) as? [String: Any] else { return nil } - - return jsonData - } -} - - public struct HTTPService { public static func makeRequest( _ request: Routable, @@ -139,15 +81,6 @@ public struct HTTPService { } } -public struct SpecDecodableModel { - public static func create(from fixture: [String: Any]) -> T? { - let decoder = JSONDecoder() - decoder.dateDecodingStrategyFormatters = [DateUtils.dateFormatter, DateUtils.ymdDateFormatter] - let fixtureData = Data(fixture.jsonString!.utf8) - return try? decoder.decode(T.self, from: fixtureData) - } -} - private extension JSONDecoder { var dateDecodingStrategyFormatters: [DateFormatter]? { get { diff --git a/SaltedgeAuthenticatorCore/Classes/Helpers/DateUtils.swift b/SaltedgeAuthenticatorCore/Classes/Helpers/DateUtils.swift index c61280ba..9ebd6379 100644 --- a/SaltedgeAuthenticatorCore/Classes/Helpers/DateUtils.swift +++ b/SaltedgeAuthenticatorCore/Classes/Helpers/DateUtils.swift @@ -27,11 +27,11 @@ public struct DateUtils { return shared.iso8601dateFormatter } - static var dateFormatter: DateFormatter { + public static var dateFormatter: DateFormatter { return shared.dateFormatter } - static var ymdDateFormatter: DateFormatter { + public static var ymdDateFormatter: DateFormatter { return shared.ymdDateFormatter } diff --git a/SaltedgeAuthenticatorCore/Classes/Helpers/TypeAliases.swift b/SaltedgeAuthenticatorCore/Classes/Helpers/TypeAliases.swift index 57df6c75..d5986cbb 100644 --- a/SaltedgeAuthenticatorCore/Classes/Helpers/TypeAliases.swift +++ b/SaltedgeAuthenticatorCore/Classes/Helpers/TypeAliases.swift @@ -25,7 +25,6 @@ public typealias ApiVersion = String public typealias SuccessBlock = () -> () public typealias FailureBlock = (String) -> () public typealias RequestSuccessBlock = ([String: Any]?) -> () -public typealias HTTPServiceSuccessClosure = (T) -> () public typealias AccessToken = String public typealias GUID = String diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SESubmitActionResponse.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SESubmitActionResponse.swift index 34cac122..c7bc92b5 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SESubmitActionResponse.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SESubmitActionResponse.swift @@ -23,8 +23,7 @@ import Foundation import SEAuthenticatorCore -public struct SESubmitActionResponse: SEBaseActionResponse { - +public struct SESubmitActionResponse: SEBaseActionResponse { public let success: Bool public var authorizationId: String? public var connectionId: String? diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponseV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponseV2.swift index 9634f2eb..1e0a24f1 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponseV2.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponseV2.swift @@ -49,7 +49,6 @@ public struct SEProviderResponseV2: Decodable { } public init(from decoder: Decoder) throws { - print("init decoder") let container = try decoder.container(keyedBy: CodingKeys.self) let dataContainer = try container.nestedContainer(keyedBy: DataCodingKeys.self, forKey: .data) name = try dataContainer.decode(String.self, forKey: .name) @@ -61,6 +60,5 @@ public struct SEProviderResponseV2: Decodable { providerId = "\(id)" publicKey = try dataContainer.decode(String.self, forKey: .publicKey) geolocationRequired = try dataContainer.decodeIfPresent(Bool.self, forKey: .geolocationRequired) - print("init name \(name), baseUrl: \(baseUrl)") } } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/SEEncryptedAuthorizationData.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/SEEncryptedAuthorizationData.swift index bbd4c98d..84ce847c 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/SEEncryptedAuthorizationData.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/SEEncryptedAuthorizationData.swift @@ -30,7 +30,7 @@ public struct SEEncryptedAuthorizationData: SEBaseEncryptedAuthorizationData, De public let iv: String public let status: AuthorizationStatus public var connectionId: String? - public var entityId: String? //TODO: check if we need it + public var entityId: String? enum CodingKeys: String, CodingKey { case id From ee9b5b99a9acb806cb516fa49db60f60db5259c8 Mon Sep 17 00:00:00 2001 From: mnewlive Date: Fri, 3 Sep 2021 17:44:54 +0300 Subject: [PATCH 082/110] Modified createNotEncryptedAuthResponseV2 --- .../Data Sources/AuthorizationsDataSourceSpec.swift | 2 +- Example/Tests/Spec Helpers/SpecUtils.swift | 13 ++----------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift b/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift index 492dac4a..9937e23c 100644 --- a/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift +++ b/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift @@ -237,7 +237,7 @@ class AuthorizationsDataSourceSpec: BaseSpec { _ = dataSource.update(with: [finalStatusAuthorization]) - expect(dataSource.rows).to(equal(1)) + expect(dataSource.rows).to(equal(0)) } } } diff --git a/Example/Tests/Spec Helpers/SpecUtils.swift b/Example/Tests/Spec Helpers/SpecUtils.swift index 50a7b49a..46f2b370 100644 --- a/Example/Tests/Spec Helpers/SpecUtils.swift +++ b/Example/Tests/Spec Helpers/SpecUtils.swift @@ -90,17 +90,8 @@ struct SpecUtils { guid: GUID, status: AuthorizationStatus ) -> SEAuthorizationDataV2 { - let dict: [String: Any] = [ - "data": "", - "key": "", - "iv": "", - "id": authorizationId, - "connection_id": connectionId, - "status": "denied" - ] - - let response = SpecDecodableModel.create(from: dict) - return (response?.decryptedAuthorizationDataV2!)! + + return SEAuthorizationDataV2(id: "(authorizationId)", connectionId: "(connectionId)", status: status) } From 9fcd523bc2b736272ce72fee1b88aa608fe78642 Mon Sep 17 00:00:00 2001 From: mnewlive Date: Fri, 3 Sep 2021 18:22:29 +0300 Subject: [PATCH 083/110] Modified tests, rm unused imports and classes --- .../ConfirmAuthorizationResponseSpec.swift | 1 - .../CreateConnectionResponseSpec.swift | 1 - .../Responses/ProviderResponseSpec.swift | 1 - .../RevokeConnectionResponseSpec.swift | 1 - .../Responses/SubmitActionResponseSpec.swift | 1 - Example/Tests/Spec Helpers/SpecUtils.swift | 5 +- .../API/Networking/BaseNetworking.swift | 29 ----------- .../Classes/API/Networking/HTTPService.swift | 51 ------------------- .../Classes/API/SerializableResponse.swift | 27 ---------- 9 files changed, 2 insertions(+), 115 deletions(-) delete mode 100644 SaltedgeAuthenticatorCore/Classes/API/Networking/BaseNetworking.swift delete mode 100644 SaltedgeAuthenticatorCore/Classes/API/Networking/HTTPService.swift delete mode 100644 SaltedgeAuthenticatorCore/Classes/API/SerializableResponse.swift diff --git a/Example/Tests/Networking/Responses/ConfirmAuthorizationResponseSpec.swift b/Example/Tests/Networking/Responses/ConfirmAuthorizationResponseSpec.swift index 24b8ee0e..c784fb37 100644 --- a/Example/Tests/Networking/Responses/ConfirmAuthorizationResponseSpec.swift +++ b/Example/Tests/Networking/Responses/ConfirmAuthorizationResponseSpec.swift @@ -23,7 +23,6 @@ import Quick import Nimble @testable import SEAuthenticator -@testable import SEAuthenticatorCore class ConfirmAuthorizationResponseSpec: BaseSpec { override func spec() { diff --git a/Example/Tests/Networking/Responses/CreateConnectionResponseSpec.swift b/Example/Tests/Networking/Responses/CreateConnectionResponseSpec.swift index 5df5c71d..857f8d32 100644 --- a/Example/Tests/Networking/Responses/CreateConnectionResponseSpec.swift +++ b/Example/Tests/Networking/Responses/CreateConnectionResponseSpec.swift @@ -23,7 +23,6 @@ import Quick import Nimble @testable import SEAuthenticator -@testable import SEAuthenticatorCore class CreateConnectionResponseSpec: BaseSpec { override func spec() { diff --git a/Example/Tests/Networking/Responses/ProviderResponseSpec.swift b/Example/Tests/Networking/Responses/ProviderResponseSpec.swift index 9e3fe3f2..6906179f 100644 --- a/Example/Tests/Networking/Responses/ProviderResponseSpec.swift +++ b/Example/Tests/Networking/Responses/ProviderResponseSpec.swift @@ -23,7 +23,6 @@ import Quick import Nimble @testable import SEAuthenticator -@testable import SEAuthenticatorCore class ProviderResponseSpec: BaseSpec { override func spec() { diff --git a/Example/Tests/Networking/Responses/RevokeConnectionResponseSpec.swift b/Example/Tests/Networking/Responses/RevokeConnectionResponseSpec.swift index 906ca8d7..b1c447fd 100644 --- a/Example/Tests/Networking/Responses/RevokeConnectionResponseSpec.swift +++ b/Example/Tests/Networking/Responses/RevokeConnectionResponseSpec.swift @@ -23,7 +23,6 @@ import Quick import Nimble @testable import SEAuthenticator -@testable import SEAuthenticatorCore class RevokeConnectionResponseSpec: BaseSpec { override func spec() { diff --git a/Example/Tests/Networking/Responses/SubmitActionResponseSpec.swift b/Example/Tests/Networking/Responses/SubmitActionResponseSpec.swift index 4953cd51..4978da83 100644 --- a/Example/Tests/Networking/Responses/SubmitActionResponseSpec.swift +++ b/Example/Tests/Networking/Responses/SubmitActionResponseSpec.swift @@ -23,7 +23,6 @@ import Quick import Nimble @testable import SEAuthenticator -@testable import SEAuthenticatorCore class SubmitActionResponseSpec: BaseSpec { override func spec() { diff --git a/Example/Tests/Spec Helpers/SpecUtils.swift b/Example/Tests/Spec Helpers/SpecUtils.swift index 46f2b370..0665db70 100644 --- a/Example/Tests/Spec Helpers/SpecUtils.swift +++ b/Example/Tests/Spec Helpers/SpecUtils.swift @@ -79,8 +79,8 @@ struct SpecUtils { "status": status ] - let response = SpecDecodableModel.create(from: dict) - return (response?.decryptedAuthorizationDataV2!)! + let response = SpecDecodableModel.create(from: dict)! + return response.decryptedAuthorizationDataV2! } static func createNotEncryptedAuthResponseV2( @@ -90,7 +90,6 @@ struct SpecUtils { guid: GUID, status: AuthorizationStatus ) -> SEAuthorizationDataV2 { - return SEAuthorizationDataV2(id: "(authorizationId)", connectionId: "(connectionId)", status: status) } diff --git a/SaltedgeAuthenticatorCore/Classes/API/Networking/BaseNetworking.swift b/SaltedgeAuthenticatorCore/Classes/API/Networking/BaseNetworking.swift deleted file mode 100644 index 55fd08a9..00000000 --- a/SaltedgeAuthenticatorCore/Classes/API/Networking/BaseNetworking.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// BaseNetworking -// This file is part of the Salt Edge Authenticator distribution -// (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2021 Salt Edge Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 3 or later. -// -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -// For the additional permissions granted for Salt Edge Authenticator -// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md -// - -import Foundation - -protocol Networking { - static func execute(_ urlRequest: Routable, success: @escaping RequestSuccessBlock, failure: @escaping FailureBlock) -} - -struct BaseNetworking: Networking {} diff --git a/SaltedgeAuthenticatorCore/Classes/API/Networking/HTTPService.swift b/SaltedgeAuthenticatorCore/Classes/API/Networking/HTTPService.swift deleted file mode 100644 index 62c4eb19..00000000 --- a/SaltedgeAuthenticatorCore/Classes/API/Networking/HTTPService.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// HTTPService -// This file is part of the Salt Edge Authenticator distribution -// (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2021 Salt Edge Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 3 or later. -// -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -// For the additional permissions granted for Salt Edge Authenticator -// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md -// - -import Foundation - -public struct HTTPService { - public static func execute( - request: Routable, - success: @escaping HTTPServiceSuccessClosure, - failure: @escaping FailureBlock - ) { - BaseNetworking.execute( - request, - success: { response in - if let data = T(response ?? [:]) { - success(data) - } else { - failure(handleErrorResponse(response)) - } - }, - failure: failure - ) - } - - private static func handleErrorResponse(_ response: [String: Any]?) -> String { - if let errorMessage = response?[SENetKeys.message] as? String { - return errorMessage - } - - return "Something went wrong" - } -} diff --git a/SaltedgeAuthenticatorCore/Classes/API/SerializableResponse.swift b/SaltedgeAuthenticatorCore/Classes/API/SerializableResponse.swift deleted file mode 100644 index 0156b9c2..00000000 --- a/SaltedgeAuthenticatorCore/Classes/API/SerializableResponse.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// SerializableResponse.swift -// This file is part of the Salt Edge Authenticator distribution -// (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2021 Salt Edge Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 3 or later. -// -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -// For the additional permissions granted for Salt Edge Authenticator -// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md -// - -import Foundation - -public protocol SerializableResponse { - init?(_ value: Any) -} From 3c8cf0da181493a2255620e1284e33a6be258b36 Mon Sep 17 00:00:00 2001 From: mnewlive Date: Fri, 3 Sep 2021 18:47:27 +0300 Subject: [PATCH 084/110] Added missing interpolation --- .../Models/Data Sources/AuthorizationsDataSourceSpec.swift | 2 +- Example/Tests/Spec Helpers/SpecUtils.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift b/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift index 9937e23c..492dac4a 100644 --- a/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift +++ b/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift @@ -237,7 +237,7 @@ class AuthorizationsDataSourceSpec: BaseSpec { _ = dataSource.update(with: [finalStatusAuthorization]) - expect(dataSource.rows).to(equal(0)) + expect(dataSource.rows).to(equal(1)) } } } diff --git a/Example/Tests/Spec Helpers/SpecUtils.swift b/Example/Tests/Spec Helpers/SpecUtils.swift index 0665db70..05d0f197 100644 --- a/Example/Tests/Spec Helpers/SpecUtils.swift +++ b/Example/Tests/Spec Helpers/SpecUtils.swift @@ -90,7 +90,7 @@ struct SpecUtils { guid: GUID, status: AuthorizationStatus ) -> SEAuthorizationDataV2 { - return SEAuthorizationDataV2(id: "(authorizationId)", connectionId: "(connectionId)", status: status) + return SEAuthorizationDataV2(id: "\(authorizationId)", connectionId: "\(connectionId)", status: status) } From 06162211f9fd374cab2ddc33ba25b5e8b8e9be31 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Mon, 16 May 2022 12:26:32 +0300 Subject: [PATCH 085/110] fixed access token decryption on connection save --- .../Authenticator.xcodeproj/project.pbxproj | 182 ++++++++++++-- .../descriptionYellow.colorset/Contents.json | 20 ++ .../extraTextColor.colorset/Contents.json | 20 ++ .../Base.lproj/LaunchScreen.storyboard | 4 +- .../AuthorizationsCoordinator.swift | 6 +- .../Connect/InstantActionCoordinator.swift | 9 +- .../Connect/QRCodeCoordinator.swift | 12 +- .../Coordinators/ConnectViewCoordinator.swift | 15 +- .../Connections/ConnectionsCoordinator.swift | 30 ++- .../ConsentDetailCoordinator.swift | 2 +- .../Main/ApplicationCoordinator.swift | 9 +- .../Migrations/AddConnectionV2Fields.swift | 39 +++ .../Database/RealmMigrationManager.swift | 3 +- .../Collectors/ConnectionsCollector.swift | 4 + .../AuthorizationsDataSource.swift | 88 +++++-- .../Models/Database/Connection.swift | 61 ++++- .../Repositories/ConnectionRepository.swift | 10 +- .../Supporting Files/AppDelegate.swift | 19 +- .../en.lproj/Authenticator.strings | 14 +- .../Utils/Components Styles/LabelStyles.swift | 1 + .../Utils/Extensions/ColorExtensions.swift | 8 + .../SEEncryptedDataExtensions.swift | 30 ++- .../Extensions/StackViewExtensions.swift | 8 + .../Utils/Extensions/StringExtensions.swift | 8 + .../Utils/Handlers/ConnectHandler.swift | 25 +- .../Utils/Handlers/InstantActionHandler.swift | 122 ++++++--- ...anager.swift => ConnectivityManager.swift} | 10 +- .../Utils/Helpers/Constants.swift | 2 + .../Utils/Helpers/Localizations.swift | 11 + .../Utils/Helpers/LocationManager.swift | 9 +- .../Utils/Helpers/NotificationsHelper.swift | 1 + .../AuthorizationsInteractor.swift | 90 ------- .../Base/AuthorizationsInteractor.swift | 126 ++++++++++ .../Base/BaseConnectionsInteractor.swift | 42 ++++ .../{ => Base}/CollectionsInteractor.swift | 90 +++++-- .../{ => Base}/ConsentsInteractor.swift | 29 ++- .../{ => v1}/ConnectionsInteractor.swift | 22 +- .../v2/ConnectionsInteractorV2.swift | 157 ++++++++++++ .../AuthorizationsViewController.swift | 38 ++- .../Connection/ConnectViewController.swift | 38 +-- .../ConnectionsViewController.swift | 57 +++-- .../ConnectorWebViewController.swift | 2 +- .../QRCodeViewController.swift | 3 +- .../Settings/SettingsViewController.swift | 14 +- .../AuthorizationDetailViewModel.swift | 48 +++- .../AuthorizationsViewModel.swift | 105 +++++--- .../SingleAuthorizationViewModel.swift | 78 +++--- .../Connections/ConnectViewModel.swift | 47 ++++ .../Connections/ConnectionCellViewModel.swift | 53 +++- .../ConnectionPickerViewModel.swift | 23 +- .../Connections/ConnectionsViewModel.swift | 51 +++- .../Connections/ConsentDetailViewModel.swift | 4 +- .../Connections/ConsentViewModel.swift | 3 +- ...AuthorizationContentDynamicStackView.swift | 149 +++++++++++ .../AuthorizationContentView.swift | 70 ++++-- .../AuthorizationStateView.swift | 29 ++- .../Authorizations/AuthorizationView.swift | 9 +- .../Consents/ConsentSharedDataView.swift | 2 +- Example/Podfile | 6 +- Example/Podfile.lock | 178 ++++++++------ Example/Tests/Errors/CryptoErrorsSpec.swift | 1 + .../Tests/Extensions/DateExtensionsSpec.swift | 8 + .../SEEncryptedDataExtensionsSpec.swift | 67 ++++- .../Extensions/StackViewExtensionsSpec.swift | 76 ++++++ .../Extensions/StringExtensionsSpec.swift | 36 +++ .../Helpers/ApiVersionExtractorSpec.swift | 47 ++++ .../Helpers/AuthorizationStatusSpec.swift | 169 +++++++++++++ Example/Tests/Helpers/DateUtilsSpec.swift | 2 +- .../Tests/Helpers/SEConnectHelperSpec.swift | 70 +++++- .../Collectors/ConnectionCollectorSpec.swift | 15 ++ .../AuthorizationsDataSourceSpec.swift | 156 +++++++++--- .../Models/Database/ConnectionSpec.swift | 20 ++ .../Structures/ConnectionDataSpec.swift | 1 + .../ConfirmAuthorizationResponseSpec.swift | 4 +- .../CreateConnectionResponseSpec.swift | 12 +- .../Responses/ProviderResponseSpec.swift | 16 +- .../RevokeConnectionResponseSpec.swift | 6 +- .../Responses/SubmitActionResponseSpec.swift | 4 +- .../Routers/{ => v1}/ActionRouterSpec.swift | 5 +- .../{ => v1}/AuthorizationRouterSpec.swift | 1 + .../{ => v1}/ConnectionRouterSpec.swift | 3 +- .../Routers/{ => v1}/ProviderRouterSpec.swift | 1 + .../Tests/Spec Helpers/MockConnectable.swift | 11 +- .../Spec Helpers/MockLocationManager.swift | 2 +- .../Tests/Spec Helpers/SpecCryptoHelper.swift | 1 + Example/Tests/Spec Helpers/SpecUtils.swift | 93 ++++++- .../Spec Helpers/URLRequestBuilder.swift | 18 +- .../AuthorizationsViewModelSpec.swift | 82 ++++++- .../ViewModels/ConnectViewModelSpec.swift | 74 ++++++ .../ViewModels/ConnectionsViewModelSpec.swift | 229 +++++++++++++++++ Example/Tests/v2/JWSHelperSpec.swift | 72 ++++++ .../RequestParametersBuilderSpecV2.swift | 82 +++++++ .../Routers/SEAuthorizationRouterSpecV2.swift | 155 ++++++++++++ .../Routers/SEConnectionRouterSpecV2.swift | 97 ++++++++ Gemfile.lock | 3 + README.md | 7 + .../Classes/API/Models}/BaseStructures.swift | 8 +- .../API/Models/SEBaseActionResponse.swift | 11 +- .../API/Models/SEBaseAuthorizationData.swift | 24 +- .../SEBaseEncryptedAuthorizationData.swift | 31 +++ .../Models/SEConfirmAuthorizationData.swift | 5 +- .../Classes/API/Models/SEConsentData.swift | 26 +- .../Classes/API/Networking/Networking.swift | 107 ++++++++ .../API/Networking}/URLSessionManager.swift | 4 +- .../Classes/API/SEConnectHelper.swift | 40 ++- .../Classes/API}/SEEncryptedData.swift | 42 +++- .../Classes/API/SENetConstants.swift | 8 +- .../Classes/API/SENetKeys.swift | 26 +- .../Classes/API/SENetPathBuilder.swift | 24 +- .../Classes/Crypto/SECryptoHelper.swift | 93 +++++-- .../Classes/Crypto/SECryptoHelperError.swift | 6 +- .../Classes/Crypto/SETagHelper.swift | 0 .../Classes}/DateExtensions.swift | 22 +- .../Classes}/DictionaryExtensions.swift | 4 +- .../Classes/Helpers/ApiVersionExtractor.swift | 34 +++ .../Classes/Helpers/AuthorizationStatus.swift | 52 ++++ .../Classes/Helpers/DateUtils.swift | 28 ++- .../Helpers/DeviceModelExtension.swift | 231 ++++++++++++++++++ .../Helpers/ParametersSerializer.swift | 6 +- .../Classes/Helpers/SEPollingTimer.swift | 5 +- .../Classes/Helpers/TypeAliases.swift | 11 +- .../Classes}/Routers/Routable.swift | 28 ++- .../Classes/SEWebView/SEWebView.swift | 2 +- .../Classes}/URLExtensions.swift | 4 +- .../SaltedgeAuthenticatorCore.podspec | 31 +++ SaltedgeAuthenticatorSDK.podspec | 3 +- .../API/Managers/SEActionManager.swift | 9 +- .../API/Managers/SEAuthorizationManager.swift | 33 +-- .../API/Managers/SEConnectionManager.swift | 17 +- ...tsManager.swift => SEConsentManager.swift} | 21 +- .../API/Managers/SEProviderManager.swift | 9 +- .../Models/Requests/SEActionRequestData.swift | 1 + .../SECreateConnectionRequestData.swift | 1 + .../Responses/AuthorizationResponses.swift | 65 ----- .../SEConfirmAuthorizationResponse.swift | 45 ++++ .../SECreateConnectionResponse.swift | 29 ++- .../Models/Responses/SEProviderResponse.swift | 52 ++-- .../SERevokeConnectionResponse.swift | 23 +- .../Responses/SERevokeConsentResponse.swift | 27 +- .../Responses/SESubmitActionResponse.swift | 33 +-- .../API/Models/SEAuthorizationData.swift | 11 +- .../Classes/API/Networking.swift | 75 ------ .../API/RequestParametersBuilder.swift | 1 + .../Classes/API/Routers/SEActionRouter.swift | 7 +- .../API/Routers/SEAuthorizationRouter.swift | 11 +- .../API/Routers/SEConnectionRouter.swift | 7 +- ...entsRouter.swift => SEConsentRouter.swift} | 9 +- .../API/Routers/SEProviderRouter.swift | 1 + .../Classes/Helpers/SignatureHelper.swift | 13 +- .../Classes/API/Headers.swift | 72 ++++++ .../Classes/API/Helpers/ApiConstants.swift | 40 +++ .../API/Managers/SEActionManagerV2.swift | 38 +++ .../Managers/SEAuthorizationManagerV2.swift | 95 +++++++ .../API/Managers/SEConnectionManagerV2.swift | 52 ++++ .../API/Managers/SEConsentManagerV2.swift | 52 ++++ .../API/Managers/SEProviderManagerV2.swift | 38 +++ .../API/Models/SEActionRequestDataV2.swift | 50 ++++ .../API/Models/SEAuthorizationDataV2.swift | 78 ++++++ .../Models/SEAuthorizationRequestData.swift | 42 ++++ .../Responses/AuthorizationResponses.swift | 50 ++++ .../SEConfirmAuthorizationResponseV2.swift | 46 ++++ .../SECreateConnectionResponse.swift | 41 ++-- .../API/Responses/SEProviderResponseV2.swift | 64 +++++ .../SERevokeConnectionResponse.swift | 43 ++++ .../Responses/SERevokeConsentResponseV2.swift | 43 ++++ .../Responses/SESubmitActionResponseV2.swift | 47 ++++ .../API/Routers/SEActionRouterV2.swift | 61 +++++ .../API/Routers/SEAuthorizationRouter.swift | 88 +++++++ .../API/Routers/SEConnectionRouter.swift | 92 +++++++ .../API/Routers/SEConsentRouterV2.swift | 78 ++++++ .../API/Routers/SEProviderRouterV2.swift | 52 ++++ .../API/SEEncryptedAuthorizationData.swift | 56 +++++ .../Classes/JWSHelper.swift | 52 ++++ .../Classes/RequestParametersBuilder.swift | 103 ++++++++ .../SaltedgeAuthenticatorSDKv2.podspec | 33 +++ 175 files changed, 5869 insertions(+), 1044 deletions(-) create mode 100644 Example/Authenticator/Assets.xcassets/Colors/descriptionYellow.colorset/Contents.json create mode 100644 Example/Authenticator/Assets.xcassets/Colors/extraTextColor.colorset/Contents.json create mode 100644 Example/Authenticator/Database/Migrations/AddConnectionV2Fields.swift rename Example/Authenticator/Utils/Helpers/{ReachabilityManager.swift => ConnectivityManager.swift} (93%) delete mode 100644 Example/Authenticator/Utils/Interactors/AuthorizationsInteractor.swift create mode 100644 Example/Authenticator/Utils/Interactors/Base/AuthorizationsInteractor.swift create mode 100644 Example/Authenticator/Utils/Interactors/Base/BaseConnectionsInteractor.swift rename Example/Authenticator/Utils/Interactors/{ => Base}/CollectionsInteractor.swift (57%) rename Example/Authenticator/Utils/Interactors/{ => Base}/ConsentsInteractor.swift (68%) rename Example/Authenticator/Utils/Interactors/{ => v1}/ConnectionsInteractor.swift (89%) create mode 100644 Example/Authenticator/Utils/Interactors/v2/ConnectionsInteractorV2.swift create mode 100644 Example/Authenticator/View Models/Connections/ConnectViewModel.swift create mode 100644 Example/Authenticator/Views/Authorizations/AuthorizationContentDynamicStackView.swift create mode 100644 Example/Tests/Extensions/StackViewExtensionsSpec.swift create mode 100644 Example/Tests/Extensions/StringExtensionsSpec.swift create mode 100644 Example/Tests/Helpers/ApiVersionExtractorSpec.swift create mode 100644 Example/Tests/Helpers/AuthorizationStatusSpec.swift rename Example/Tests/Networking/Routers/{ => v1}/ActionRouterSpec.swift (92%) rename Example/Tests/Networking/Routers/{ => v1}/AuthorizationRouterSpec.swift (99%) rename Example/Tests/Networking/Routers/{ => v1}/ConnectionRouterSpec.swift (96%) rename Example/Tests/Networking/Routers/{ => v1}/ProviderRouterSpec.swift (98%) rename SaltedgeAuthenticatorSDK/Classes/API/SerializableResponse.swift => Example/Tests/Spec Helpers/MockConnectable.swift (83%) create mode 100644 Example/Tests/ViewModels/ConnectViewModelSpec.swift create mode 100644 Example/Tests/ViewModels/ConnectionsViewModelSpec.swift create mode 100644 Example/Tests/v2/JWSHelperSpec.swift create mode 100644 Example/Tests/v2/Networking/RequestParametersBuilderSpecV2.swift create mode 100644 Example/Tests/v2/Networking/Routers/SEAuthorizationRouterSpecV2.swift create mode 100644 Example/Tests/v2/Networking/Routers/SEConnectionRouterSpecV2.swift rename {SaltedgeAuthenticatorSDK/Classes/API/Models/Requests => SaltedgeAuthenticatorCore/Classes/API/Models}/BaseStructures.swift (90%) rename SaltedgeAuthenticatorSDK/Classes/API/BaseNetworking.swift => SaltedgeAuthenticatorCore/Classes/API/Models/SEBaseActionResponse.swift (78%) rename SaltedgeAuthenticatorSDK/Classes/API/SENetPaths.swift => SaltedgeAuthenticatorCore/Classes/API/Models/SEBaseAuthorizationData.swift (73%) create mode 100644 SaltedgeAuthenticatorCore/Classes/API/Models/SEBaseEncryptedAuthorizationData.swift rename SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/SEConfirmAuthorizationRequestData.swift => SaltedgeAuthenticatorCore/Classes/API/Models/SEConfirmAuthorizationData.swift (95%) rename {SaltedgeAuthenticatorSDK => SaltedgeAuthenticatorCore}/Classes/API/Models/SEConsentData.swift (87%) create mode 100644 SaltedgeAuthenticatorCore/Classes/API/Networking/Networking.swift rename {SaltedgeAuthenticatorSDK/Classes/API/Managers => SaltedgeAuthenticatorCore/Classes/API/Networking}/URLSessionManager.swift (95%) rename {SaltedgeAuthenticatorSDK => SaltedgeAuthenticatorCore}/Classes/API/SEConnectHelper.swift (56%) rename {SaltedgeAuthenticatorSDK/Classes/API/Models => SaltedgeAuthenticatorCore/Classes/API}/SEEncryptedData.swift (60%) rename {SaltedgeAuthenticatorSDK => SaltedgeAuthenticatorCore}/Classes/API/SENetConstants.swift (84%) rename {SaltedgeAuthenticatorSDK => SaltedgeAuthenticatorCore}/Classes/API/SENetKeys.swift (75%) rename Example/Authenticator/Utils/Helpers/ParametersSerializer.swift => SaltedgeAuthenticatorCore/Classes/API/SENetPathBuilder.swift (68%) rename {SaltedgeAuthenticatorSDK => SaltedgeAuthenticatorCore}/Classes/Crypto/SECryptoHelper.swift (82%) rename {SaltedgeAuthenticatorSDK => SaltedgeAuthenticatorCore}/Classes/Crypto/SECryptoHelperError.swift (95%) rename {SaltedgeAuthenticatorSDK => SaltedgeAuthenticatorCore}/Classes/Crypto/SETagHelper.swift (100%) rename {SaltedgeAuthenticatorSDK/Classes/Extensions => SaltedgeAuthenticatorCore/Classes}/DateExtensions.swift (71%) rename {SaltedgeAuthenticatorSDK/Classes/Extensions => SaltedgeAuthenticatorCore/Classes}/DictionaryExtensions.swift (95%) create mode 100644 SaltedgeAuthenticatorCore/Classes/Helpers/ApiVersionExtractor.swift create mode 100644 SaltedgeAuthenticatorCore/Classes/Helpers/AuthorizationStatus.swift rename {SaltedgeAuthenticatorSDK => SaltedgeAuthenticatorCore}/Classes/Helpers/DateUtils.swift (66%) create mode 100644 SaltedgeAuthenticatorCore/Classes/Helpers/DeviceModelExtension.swift rename {SaltedgeAuthenticatorSDK/Classes/API => SaltedgeAuthenticatorCore/Classes}/Helpers/ParametersSerializer.swift (90%) rename {SaltedgeAuthenticatorSDK => SaltedgeAuthenticatorCore}/Classes/Helpers/SEPollingTimer.swift (96%) rename {SaltedgeAuthenticatorSDK => SaltedgeAuthenticatorCore}/Classes/Helpers/TypeAliases.swift (77%) rename {SaltedgeAuthenticatorSDK/Classes/API => SaltedgeAuthenticatorCore/Classes}/Routers/Routable.swift (67%) rename {SaltedgeAuthenticatorSDK => SaltedgeAuthenticatorCore}/Classes/SEWebView/SEWebView.swift (98%) rename {SaltedgeAuthenticatorSDK/Classes/Extensions => SaltedgeAuthenticatorCore/Classes}/URLExtensions.swift (95%) create mode 100644 SaltedgeAuthenticatorCore/SaltedgeAuthenticatorCore.podspec rename SaltedgeAuthenticatorSDK/Classes/API/Managers/{SEConsentsManager.swift => SEConsentManager.swift} (72%) delete mode 100644 SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/AuthorizationResponses.swift create mode 100644 SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SEConfirmAuthorizationResponse.swift delete mode 100644 SaltedgeAuthenticatorSDK/Classes/API/Networking.swift rename SaltedgeAuthenticatorSDK/Classes/API/Routers/{SEConsentsRouter.swift => SEConsentRouter.swift} (90%) create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Headers.swift create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Helpers/ApiConstants.swift create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEActionManagerV2.swift create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEAuthorizationManagerV2.swift create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConnectionManagerV2.swift create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConsentManagerV2.swift create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEProviderManagerV2.swift create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEActionRequestDataV2.swift create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEAuthorizationDataV2.swift create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEAuthorizationRequestData.swift create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Responses/AuthorizationResponses.swift create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEConfirmAuthorizationResponseV2.swift rename SaltedgeAuthenticatorSDK/Classes/API/HTTPService.swift => SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SECreateConnectionResponse.swift (52%) create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponseV2.swift create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConnectionResponse.swift create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConsentResponseV2.swift create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SESubmitActionResponseV2.swift create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEActionRouterV2.swift create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEAuthorizationRouter.swift create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConnectionRouter.swift create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConsentRouterV2.swift create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEProviderRouterV2.swift create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/API/SEEncryptedAuthorizationData.swift create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/JWSHelper.swift create mode 100644 SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift create mode 100644 SaltedgeAuthenticatorSDKv2/SaltedgeAuthenticatorSDKv2.podspec diff --git a/Example/Authenticator.xcodeproj/project.pbxproj b/Example/Authenticator.xcodeproj/project.pbxproj index 0970bee1..320cf273 100644 --- a/Example/Authenticator.xcodeproj/project.pbxproj +++ b/Example/Authenticator.xcodeproj/project.pbxproj @@ -9,6 +9,14 @@ /* Begin PBXBuildFile section */ 0479C5522387F8C300F3FE0A /* AVCaptureHelperSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0479C5512387F8C300F3FE0A /* AVCaptureHelperSpec.swift */; }; 6CF5A0163F3E926E5B271FBE /* Pods_Authenticator_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC7966F04DE3EFEB4C47030C /* Pods_Authenticator_Tests.framework */; }; + 8432878826CE378400F77DD5 /* AuthorizationStatusSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8432878726CE378400F77DD5 /* AuthorizationStatusSpec.swift */; }; + 8434188C26BD33180027CF1B /* ConnectViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8434188B26BD33180027CF1B /* ConnectViewModel.swift */; }; + 8434188D26BD33180027CF1B /* ConnectViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8434188B26BD33180027CF1B /* ConnectViewModel.swift */; }; + 8434189026BD41A20027CF1B /* ConnectViewModelSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8434188E26BD41A20027CF1B /* ConnectViewModelSpec.swift */; }; + 8434189226BD6FFB0027CF1B /* MockConnectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8434189126BD6FFB0027CF1B /* MockConnectable.swift */; }; + 84726A0726B98538005C792E /* AddConnectionV2Fields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84726A0626B98538005C792E /* AddConnectionV2Fields.swift */; }; + 84726A0826B98538005C792E /* AddConnectionV2Fields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84726A0626B98538005C792E /* AddConnectionV2Fields.swift */; }; + 8480F53B26BC182200FB5DFF /* ConnectionsViewModelSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8480F53A26BC182200FB5DFF /* ConnectionsViewModelSpec.swift */; }; 95033CC125F8FB0D00D64BDE /* MockLocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95033CC025F8FB0D00D64BDE /* MockLocationManager.swift */; }; 954CC97524A22FF30021F0B2 /* SEEncryptedDataExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED81322CE18270050ED3C /* SEEncryptedDataExtensions.swift */; }; 954CC97624A22FF40021F0B2 /* SEEncryptedDataExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED81322CE18270050ED3C /* SEEncryptedDataExtensions.swift */; }; @@ -95,6 +103,10 @@ 9D630BC7243DC094000A9ACC /* FeatureCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D630BC5243DC094000A9ACC /* FeatureCellViewModel.swift */; }; 9D630BC9243DC464000A9ACC /* OnboardingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D630BC8243DC464000A9ACC /* OnboardingViewModel.swift */; }; 9D630BCA243DC464000A9ACC /* OnboardingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D630BC8243DC464000A9ACC /* OnboardingViewModel.swift */; }; + 9D63AB5826715B4C007EE1C8 /* StringExtensionsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D63AB5726715B4C007EE1C8 /* StringExtensionsSpec.swift */; }; + 9D63AB5A26721063007EE1C8 /* AuthorizationContentDynamicStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D63AB5926721063007EE1C8 /* AuthorizationContentDynamicStackView.swift */; }; + 9D63AB5B26721063007EE1C8 /* AuthorizationContentDynamicStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D63AB5926721063007EE1C8 /* AuthorizationContentDynamicStackView.swift */; }; + 9D63AB5D2672123A007EE1C8 /* StackViewExtensionsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D63AB5C2672123A007EE1C8 /* StackViewExtensionsSpec.swift */; }; 9D6575F8232A9B0200DCC53E /* DateExtensionsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D6575F7232A9B0200DCC53E /* DateExtensionsSpec.swift */; }; 9D6575FB232A9E8100DCC53E /* DateUtilsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D6575FA232A9E8100DCC53E /* DateUtilsSpec.swift */; }; 9D66E1A922D4C1C100BD59B6 /* AuthorizationDataSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D66E17B22D4C0AF00BD59B6 /* AuthorizationDataSpec.swift */; }; @@ -111,6 +123,10 @@ 9D66E1E322D6146900BD59B6 /* ProviderResponseSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D66E1DA22D6146800BD59B6 /* ProviderResponseSpec.swift */; }; 9D66E1E522D6146900BD59B6 /* HeadersSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D66E1DC22D6146800BD59B6 /* HeadersSpec.swift */; }; 9D66E1E622D6146900BD59B6 /* RequestParametersBuilderSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D66E1DD22D6146800BD59B6 /* RequestParametersBuilderSpec.swift */; }; + 9D69071C2655439200A9CE94 /* JWSHelperSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D69071B2655439200A9CE94 /* JWSHelperSpec.swift */; }; + 9D6907402657E38A00A9CE94 /* RequestParametersBuilderSpecV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D69073F2657E38A00A9CE94 /* RequestParametersBuilderSpecV2.swift */; }; + 9D6907432657E67C00A9CE94 /* SEConnectionRouterSpecV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D6907422657E67C00A9CE94 /* SEConnectionRouterSpecV2.swift */; }; + 9D6907452657F94C00A9CE94 /* SEAuthorizationRouterSpecV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D6907442657F94C00A9CE94 /* SEAuthorizationRouterSpecV2.swift */; }; 9D772129231E50400015BAC6 /* AuthorizationsHeaderCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D772128231E50400015BAC6 /* AuthorizationsHeaderCollectionViewCell.swift */; }; 9D77212A231E50400015BAC6 /* AuthorizationsHeaderCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D772128231E50400015BAC6 /* AuthorizationsHeaderCollectionViewCell.swift */; }; 9D77212C231E50DD0015BAC6 /* AuthorizationsHeadersSwipingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D77212B231E50DD0015BAC6 /* AuthorizationsHeadersSwipingView.swift */; }; @@ -150,6 +166,8 @@ 9DBAD7A3247E65D30023F944 /* InstantActionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DBAD7A1247E65D30023F944 /* InstantActionCoordinator.swift */; }; 9DCBB28B243618B900DB3F85 /* Observable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCBB28A243618B900DB3F85 /* Observable.swift */; }; 9DCBB28C243618B900DB3F85 /* Observable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCBB28A243618B900DB3F85 /* Observable.swift */; }; + 9DCC5E25265D1DD40054B933 /* ConnectionsInteractorV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCC5E24265D1DD40054B933 /* ConnectionsInteractorV2.swift */; }; + 9DCC5E26265D1DD40054B933 /* ConnectionsInteractorV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCC5E24265D1DD40054B933 /* ConnectionsInteractorV2.swift */; }; 9DCED89A22CE18280050ED3C /* APIErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED7DD22CE18270050ED3C /* APIErrors.swift */; }; 9DCED89C22CE18280050ED3C /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED7E122CE18270050ED3C /* SettingsViewController.swift */; }; 9DCED89E22CE18280050ED3C /* ConnectionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED7E422CE18270050ED3C /* ConnectionsViewController.swift */; }; @@ -188,7 +206,6 @@ 9DCED8E722CE18280050ED3C /* PasscodeManger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED83D22CE18270050ED3C /* PasscodeManger.swift */; }; 9DCED8E922CE18280050ED3C /* NotificationsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED83F22CE18270050ED3C /* NotificationsManager.swift */; }; 9DCED8EA22CE18280050ED3C /* HapticFeedbackHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED84022CE18270050ED3C /* HapticFeedbackHelper.swift */; }; - 9DCED8EB22CE18280050ED3C /* ParametersSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED84122CE18270050ED3C /* ParametersSerializer.swift */; }; 9DCED8EC22CE18280050ED3C /* BiometricsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED84222CE18270050ED3C /* BiometricsHelper.swift */; }; 9DCED8ED22CE18280050ED3C /* AuthorizationDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED84422CE18270050ED3C /* AuthorizationDetailViewModel.swift */; }; 9DCED8EE22CE18280050ED3C /* MessageBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED84622CE18270050ED3C /* MessageBarView.swift */; }; @@ -222,14 +239,17 @@ 9DCED92A22CE18280050ED3C /* Authenticator.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9DCED89522CE18280050ED3C /* Authenticator.strings */; }; 9DCED92D22CE1DC30050ED3C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9DCED92B22CE1DC30050ED3C /* LaunchScreen.storyboard */; }; 9DCED93722D3389C0050ED3C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9DCED93622D3389C0050ED3C /* Assets.xcassets */; }; + 9DD155EC265E74C800AEFA0D /* ApiVersionExtractorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD155EB265E74C800AEFA0D /* ApiVersionExtractorSpec.swift */; }; + 9DD155F326613CEA00AEFA0D /* BaseConnectionsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD155F226613CEA00AEFA0D /* BaseConnectionsInteractor.swift */; }; + 9DD155F426613CEB00AEFA0D /* BaseConnectionsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD155F226613CEA00AEFA0D /* BaseConnectionsInteractor.swift */; }; 9DD4DF252375969A000A9B80 /* QuickActionsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD4DF242375969A000A9B80 /* QuickActionsHelper.swift */; }; 9DD4DF262375969B000A9B80 /* QuickActionsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD4DF242375969A000A9B80 /* QuickActionsHelper.swift */; }; 9DD4DF2823759767000A9B80 /* AVCaptureHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD4DF2723759767000A9B80 /* AVCaptureHelper.swift */; }; 9DD4DF2923759767000A9B80 /* AVCaptureHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD4DF2723759767000A9B80 /* AVCaptureHelper.swift */; }; 9DD5098F24742ED900CB188D /* ConnectionsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95F408FF2473C90F00727095 /* ConnectionsCoordinator.swift */; }; 9DD50992247527D800CB188D /* AuthorizationsViewModelSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD50990247527D800CB188D /* AuthorizationsViewModelSpec.swift */; }; - 9DD8945D22E9D969008F3130 /* ReachabilityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD8945C22E9D969008F3130 /* ReachabilityManager.swift */; }; - 9DD8945E22E9D969008F3130 /* ReachabilityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD8945C22E9D969008F3130 /* ReachabilityManager.swift */; }; + 9DD8945D22E9D969008F3130 /* ConnectivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD8945C22E9D969008F3130 /* ConnectivityManager.swift */; }; + 9DD8945E22E9D969008F3130 /* ConnectivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD8945C22E9D969008F3130 /* ConnectivityManager.swift */; }; 9DD8946022E9F4BD008F3130 /* NotificationsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD8945F22E9F4BD008F3130 /* NotificationsHelper.swift */; }; 9DD8946122E9F4BD008F3130 /* NotificationsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD8945F22E9F4BD008F3130 /* NotificationsHelper.swift */; }; 9DE308B92445BB05009EBD39 /* Authenticator.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9DCED89522CE18280050ED3C /* Authenticator.strings */; }; @@ -302,7 +322,6 @@ 9DE324AF22D39A2D00EB162A /* PasscodeManger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED83D22CE18270050ED3C /* PasscodeManger.swift */; }; 9DE324B122D39A3200EB162A /* NotificationsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED83F22CE18270050ED3C /* NotificationsManager.swift */; }; 9DE324B222D39A3500EB162A /* HapticFeedbackHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED84022CE18270050ED3C /* HapticFeedbackHelper.swift */; }; - 9DE324B322D39A3700EB162A /* ParametersSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED84122CE18270050ED3C /* ParametersSerializer.swift */; }; 9DE324B422D39A3A00EB162A /* BiometricsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED84222CE18270050ED3C /* BiometricsHelper.swift */; }; 9DE324B622D39A4700EB162A /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED7E122CE18270050ED3C /* SettingsViewController.swift */; }; 9DE324B822D39A4E00EB162A /* ConnectionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCED7E422CE18270050ED3C /* ConnectionsViewController.swift */; }; @@ -382,6 +401,12 @@ 607FACD01AFB9204008FA782 /* Authenticator_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Authenticator_Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 607FACE51AFB9204008FA782 /* Authenticator_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Authenticator_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 8432878726CE378400F77DD5 /* AuthorizationStatusSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorizationStatusSpec.swift; sourceTree = ""; }; + 8434188B26BD33180027CF1B /* ConnectViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectViewModel.swift; sourceTree = ""; }; + 8434188E26BD41A20027CF1B /* ConnectViewModelSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectViewModelSpec.swift; sourceTree = ""; }; + 8434189126BD6FFB0027CF1B /* MockConnectable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockConnectable.swift; sourceTree = ""; }; + 84726A0626B98538005C792E /* AddConnectionV2Fields.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddConnectionV2Fields.swift; sourceTree = ""; }; + 8480F53A26BC182200FB5DFF /* ConnectionsViewModelSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionsViewModelSpec.swift; sourceTree = ""; }; 95033CC025F8FB0D00D64BDE /* MockLocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockLocationManager.swift; sourceTree = ""; }; 957BCE5E24A3921B001F456F /* Log.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = ""; }; 9581A9DC2477E0C00066EF5E /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = ""; }; @@ -420,6 +445,9 @@ 9D5BCFE2248108F1009A6B40 /* AspectFitImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AspectFitImageView.swift; sourceTree = ""; }; 9D630BC5243DC094000A9ACC /* FeatureCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureCellViewModel.swift; sourceTree = ""; }; 9D630BC8243DC464000A9ACC /* OnboardingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewModel.swift; sourceTree = ""; }; + 9D63AB5726715B4C007EE1C8 /* StringExtensionsSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtensionsSpec.swift; sourceTree = ""; }; + 9D63AB5926721063007EE1C8 /* AuthorizationContentDynamicStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorizationContentDynamicStackView.swift; sourceTree = ""; }; + 9D63AB5C2672123A007EE1C8 /* StackViewExtensionsSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackViewExtensionsSpec.swift; sourceTree = ""; }; 9D6575F7232A9B0200DCC53E /* DateExtensionsSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateExtensionsSpec.swift; sourceTree = ""; }; 9D6575FA232A9E8100DCC53E /* DateUtilsSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateUtilsSpec.swift; sourceTree = ""; }; 9D66E17122D4C0AF00BD59B6 /* CryptoErrorsSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CryptoErrorsSpec.swift; sourceTree = ""; }; @@ -438,6 +466,10 @@ 9D66E1DA22D6146800BD59B6 /* ProviderResponseSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProviderResponseSpec.swift; sourceTree = ""; }; 9D66E1DC22D6146800BD59B6 /* HeadersSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeadersSpec.swift; sourceTree = ""; }; 9D66E1DD22D6146800BD59B6 /* RequestParametersBuilderSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestParametersBuilderSpec.swift; sourceTree = ""; }; + 9D69071B2655439200A9CE94 /* JWSHelperSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWSHelperSpec.swift; sourceTree = ""; }; + 9D69073F2657E38A00A9CE94 /* RequestParametersBuilderSpecV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestParametersBuilderSpecV2.swift; sourceTree = ""; }; + 9D6907422657E67C00A9CE94 /* SEConnectionRouterSpecV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SEConnectionRouterSpecV2.swift; sourceTree = ""; }; + 9D6907442657F94C00A9CE94 /* SEAuthorizationRouterSpecV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SEAuthorizationRouterSpecV2.swift; sourceTree = ""; }; 9D772128231E50400015BAC6 /* AuthorizationsHeaderCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorizationsHeaderCollectionViewCell.swift; sourceTree = ""; }; 9D77212B231E50DD0015BAC6 /* AuthorizationsHeadersSwipingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorizationsHeadersSwipingView.swift; sourceTree = ""; }; 9D77212E231E51200015BAC6 /* AuthorizationsCollectionLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorizationsCollectionLayout.swift; sourceTree = ""; }; @@ -459,6 +491,7 @@ 9DBAD79E247E55BD0023F944 /* QRCodeCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeCoordinator.swift; sourceTree = ""; }; 9DBAD7A1247E65D30023F944 /* InstantActionCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantActionCoordinator.swift; sourceTree = ""; }; 9DCBB28A243618B900DB3F85 /* Observable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Observable.swift; sourceTree = ""; }; + 9DCC5E24265D1DD40054B933 /* ConnectionsInteractorV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionsInteractorV2.swift; sourceTree = ""; }; 9DCED7DD22CE18270050ED3C /* APIErrors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIErrors.swift; sourceTree = ""; }; 9DCED7E022CE18270050ED3C /* LanguagePickerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LanguagePickerViewController.swift; sourceTree = ""; }; 9DCED7E122CE18270050ED3C /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; @@ -501,7 +534,6 @@ 9DCED83D22CE18270050ED3C /* PasscodeManger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeManger.swift; sourceTree = ""; }; 9DCED83F22CE18270050ED3C /* NotificationsManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationsManager.swift; sourceTree = ""; }; 9DCED84022CE18270050ED3C /* HapticFeedbackHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HapticFeedbackHelper.swift; sourceTree = ""; }; - 9DCED84122CE18270050ED3C /* ParametersSerializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParametersSerializer.swift; sourceTree = ""; }; 9DCED84222CE18270050ED3C /* BiometricsHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BiometricsHelper.swift; sourceTree = ""; }; 9DCED84422CE18270050ED3C /* AuthorizationDetailViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationDetailViewModel.swift; sourceTree = ""; }; 9DCED84622CE18270050ED3C /* MessageBarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageBarView.swift; sourceTree = ""; }; @@ -539,11 +571,13 @@ 9DCED89722CE18280050ED3C /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Authenticator.strings; sourceTree = ""; }; 9DCED92C22CE1DC30050ED3C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 9DCED93622D3389C0050ED3C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Authenticator/Assets.xcassets; sourceTree = SOURCE_ROOT; }; + 9DD155EB265E74C800AEFA0D /* ApiVersionExtractorSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiVersionExtractorSpec.swift; sourceTree = ""; }; + 9DD155F226613CEA00AEFA0D /* BaseConnectionsInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseConnectionsInteractor.swift; sourceTree = ""; }; 9DD4DF242375969A000A9B80 /* QuickActionsHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickActionsHelper.swift; sourceTree = ""; }; 9DD4DF2723759767000A9B80 /* AVCaptureHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVCaptureHelper.swift; sourceTree = ""; }; 9DD50990247527D800CB188D /* AuthorizationsViewModelSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorizationsViewModelSpec.swift; sourceTree = ""; }; 9DD8158A22DF746300B93BA8 /* Authenticator_Example.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Authenticator_Example.entitlements; sourceTree = ""; }; - 9DD8945C22E9D969008F3130 /* ReachabilityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReachabilityManager.swift; sourceTree = ""; }; + 9DD8945C22E9D969008F3130 /* ConnectivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectivityManager.swift; sourceTree = ""; }; 9DD8945F22E9F4BD008F3130 /* NotificationsHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsHelper.swift; sourceTree = ""; }; 9DE308BA2446287A009EBD39 /* PasscodeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasscodeViewController.swift; sourceTree = ""; }; 9DE308BD24474D92009EBD39 /* PasscodeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasscodeViewModel.swift; sourceTree = ""; }; @@ -675,6 +709,7 @@ 607FACE81AFB9204008FA782 /* Tests */ = { isa = PBXGroup; children = ( + 9D69071A2655437E00A9CE94 /* v2 */, 9DCBB2872435F1EF00DB3F85 /* ViewModels */, 9D66E1D222D6146800BD59B6 /* Networking */, 9D66E17022D4C0AF00BD59B6 /* Errors */, @@ -739,6 +774,7 @@ children = ( 95D784E523042BCE002BF219 /* AddConnectionSupportEmail.swift */, 95C8C07925F251E6005E787F /* AddConnectionGeolocation.swift */, + 84726A0626B98538005C792E /* AddConnectionV2Fields.swift */, ); path = Migrations; sourceTree = ""; @@ -751,6 +787,7 @@ 9D5BCFD8247FC91F009A6B40 /* ConnectionPickerViewModel.swift */, 9DE56A0724AB672500FA5B2C /* ConsentViewModel.swift */, 9D98747524B37F3500EE89BB /* ConsentDetailViewModel.swift */, + 8434188B26BD33180027CF1B /* ConnectViewModel.swift */, ); path = Connections; sourceTree = ""; @@ -779,6 +816,8 @@ children = ( 9DE3246922D398B300EB162A /* SEEncryptedDataExtensionsSpec.swift */, 9D6575F7232A9B0200DCC53E /* DateExtensionsSpec.swift */, + 9D63AB5726715B4C007EE1C8 /* StringExtensionsSpec.swift */, + 9D63AB5C2672123A007EE1C8 /* StackViewExtensionsSpec.swift */, ); path = Extensions; sourceTree = ""; @@ -789,6 +828,8 @@ 9D6575FA232A9E8100DCC53E /* DateUtilsSpec.swift */, 95CD02DC235864130085C52A /* SEConnectHelperSpec.swift */, 0479C5512387F8C300F3FE0A /* AVCaptureHelperSpec.swift */, + 9DD155EB265E74C800AEFA0D /* ApiVersionExtractorSpec.swift */, + 8432878726CE378400F77DD5 /* AuthorizationStatusSpec.swift */, ); path = Helpers; sourceTree = ""; @@ -869,10 +910,7 @@ 9D66E1D322D6146800BD59B6 /* Routers */ = { isa = PBXGroup; children = ( - 9D1DEE8023DB5177003C79AC /* ActionRouterSpec.swift */, - 9D66E1D422D6146800BD59B6 /* ConnectionRouterSpec.swift */, - 9D66E1D522D6146800BD59B6 /* AuthorizationRouterSpec.swift */, - 9D66E1D622D6146800BD59B6 /* ProviderRouterSpec.swift */, + 9D6907132655355600A9CE94 /* v1 */, ); path = Routers; sourceTree = ""; @@ -889,6 +927,44 @@ path = Responses; sourceTree = ""; }; + 9D6907132655355600A9CE94 /* v1 */ = { + isa = PBXGroup; + children = ( + 9D1DEE8023DB5177003C79AC /* ActionRouterSpec.swift */, + 9D66E1D422D6146800BD59B6 /* ConnectionRouterSpec.swift */, + 9D66E1D522D6146800BD59B6 /* AuthorizationRouterSpec.swift */, + 9D66E1D622D6146800BD59B6 /* ProviderRouterSpec.swift */, + ); + path = v1; + sourceTree = ""; + }; + 9D69071A2655437E00A9CE94 /* v2 */ = { + isa = PBXGroup; + children = ( + 9D69073E2657E37000A9CE94 /* Networking */, + 9D69071B2655439200A9CE94 /* JWSHelperSpec.swift */, + ); + path = v2; + sourceTree = ""; + }; + 9D69073E2657E37000A9CE94 /* Networking */ = { + isa = PBXGroup; + children = ( + 9D6907412657E66500A9CE94 /* Routers */, + 9D69073F2657E38A00A9CE94 /* RequestParametersBuilderSpecV2.swift */, + ); + path = Networking; + sourceTree = ""; + }; + 9D6907412657E66500A9CE94 /* Routers */ = { + isa = PBXGroup; + children = ( + 9D6907422657E67C00A9CE94 /* SEConnectionRouterSpecV2.swift */, + 9D6907442657F94C00A9CE94 /* SEAuthorizationRouterSpecV2.swift */, + ); + path = Routers; + sourceTree = ""; + }; 9DBAD79D247E55A70023F944 /* Connect */ = { isa = PBXGroup; children = ( @@ -903,8 +979,10 @@ children = ( 959E30AB247BDA030067354A /* Settings */, 9D8E5BA12445A1E70081E60E /* OnboardingViewModelSpec.swift */, + 8434188E26BD41A20027CF1B /* ConnectViewModelSpec.swift */, 9DD50990247527D800CB188D /* AuthorizationsViewModelSpec.swift */, 9DE5807C2450892E004AF19F /* PasscodeViewModelSpec.swift */, + 8480F53A26BC182200FB5DFF /* ConnectionsViewModelSpec.swift */, ); path = ViewModels; sourceTree = ""; @@ -917,10 +995,27 @@ 9DF796452497C1F7001290DA /* AuthorizationHeaderView.swift */, 9DF796482497C2D2001290DA /* AuthorizationContentView.swift */, 9DFD634723BF366C0060B29F /* AuthorizationStateView.swift */, + 9D63AB5926721063007EE1C8 /* AuthorizationContentDynamicStackView.swift */, ); path = Authorizations; sourceTree = ""; }; + 9DCC5E22265D1DB70054B933 /* v2 */ = { + isa = PBXGroup; + children = ( + 9DCC5E24265D1DD40054B933 /* ConnectionsInteractorV2.swift */, + ); + path = v2; + sourceTree = ""; + }; + 9DCC5E23265D1DBD0054B933 /* v1 */ = { + isa = PBXGroup; + children = ( + 9DCED82D22CE18270050ED3C /* ConnectionsInteractor.swift */, + ); + path = v1; + sourceTree = ""; + }; 9DCED7DC22CE18270050ED3C /* Errors */ = { isa = PBXGroup; children = ( @@ -1043,10 +1138,9 @@ 9DCED82B22CE18270050ED3C /* Interactors */ = { isa = PBXGroup; children = ( - 9DCED82C22CE18270050ED3C /* AuthorizationsInteractor.swift */, - 9DCED82D22CE18270050ED3C /* ConnectionsInteractor.swift */, - 9D98747B24B390F200EE89BB /* ConsentsInteractor.swift */, - 95C6781E24A9FD8B0088A9CF /* CollectionsInteractor.swift */, + 9DD155F126613CD800AEFA0D /* Base */, + 9DCC5E23265D1DBD0054B933 /* v1 */, + 9DCC5E22265D1DB70054B933 /* v2 */, ); path = Interactors; sourceTree = ""; @@ -1073,11 +1167,10 @@ 9DCED83722CE18270050ED3C /* Localizations.swift */, 9DCED83A22CE18270050ED3C /* AppearanceHelper.swift */, 9DCED83D22CE18270050ED3C /* PasscodeManger.swift */, - 9DD8945C22E9D969008F3130 /* ReachabilityManager.swift */, + 9DD8945C22E9D969008F3130 /* ConnectivityManager.swift */, 9DD8945F22E9F4BD008F3130 /* NotificationsHelper.swift */, 9DCED83F22CE18270050ED3C /* NotificationsManager.swift */, 9DCED84022CE18270050ED3C /* HapticFeedbackHelper.swift */, - 9DCED84122CE18270050ED3C /* ParametersSerializer.swift */, 9DCED84222CE18270050ED3C /* BiometricsHelper.swift */, 9DD4DF242375969A000A9B80 /* QuickActionsHelper.swift */, 9DCBB28A243618B900DB3F85 /* Observable.swift */, @@ -1272,6 +1365,17 @@ path = Localization; sourceTree = ""; }; + 9DD155F126613CD800AEFA0D /* Base */ = { + isa = PBXGroup; + children = ( + 9DD155F226613CEA00AEFA0D /* BaseConnectionsInteractor.swift */, + 9DCED82C22CE18270050ED3C /* AuthorizationsInteractor.swift */, + 95C6781E24A9FD8B0088A9CF /* CollectionsInteractor.swift */, + 9D98747B24B390F200EE89BB /* ConsentsInteractor.swift */, + ); + path = Base; + sourceTree = ""; + }; 9DE3242822D396F200EB162A /* TestHost-iOS */ = { isa = PBXGroup; children = ( @@ -1333,6 +1437,7 @@ 9DE3246F22D398ED00EB162A /* BaseSpec.swift */, 9DE3247022D398ED00EB162A /* DataFixtures.swift */, 95033CC025F8FB0D00D64BDE /* MockLocationManager.swift */, + 8434189126BD6FFB0027CF1B /* MockConnectable.swift */, ); path = "Spec Helpers"; sourceTree = ""; @@ -1589,12 +1694,15 @@ "${BUILT_PRODUCTS_DIR}/FirebaseInstallations/FirebaseInstallations.framework", "${BUILT_PRODUCTS_DIR}/GoogleDataTransport/GoogleDataTransport.framework", "${BUILT_PRODUCTS_DIR}/GoogleUtilities/GoogleUtilities.framework", + "${BUILT_PRODUCTS_DIR}/JOSESwift/JOSESwift.framework", "${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework", "${BUILT_PRODUCTS_DIR}/ReachabilitySwift/Reachability.framework", "${BUILT_PRODUCTS_DIR}/Realm/Realm.framework", "${BUILT_PRODUCTS_DIR}/RealmSwift/RealmSwift.framework", "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework", + "${BUILT_PRODUCTS_DIR}/SaltedgeAuthenticatorCore/SEAuthenticatorCore.framework", "${BUILT_PRODUCTS_DIR}/SaltedgeAuthenticatorSDK/SEAuthenticator.framework", + "${BUILT_PRODUCTS_DIR}/SaltedgeAuthenticatorSDKv2/SEAuthenticatorV2.framework", "${BUILT_PRODUCTS_DIR}/TinyConstraints/TinyConstraints.framework", "${BUILT_PRODUCTS_DIR}/Valet/Valet.framework", "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework", @@ -1608,12 +1716,15 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseInstallations.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleDataTransport.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleUtilities.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/JOSESwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBLPromises.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reachability.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RealmSwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SEAuthenticatorCore.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SEAuthenticator.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SEAuthenticatorV2.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TinyConstraints.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Valet.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework", @@ -1697,12 +1808,15 @@ "${BUILT_PRODUCTS_DIR}/FirebaseInstallations/FirebaseInstallations.framework", "${BUILT_PRODUCTS_DIR}/GoogleDataTransport/GoogleDataTransport.framework", "${BUILT_PRODUCTS_DIR}/GoogleUtilities/GoogleUtilities.framework", + "${BUILT_PRODUCTS_DIR}/JOSESwift/JOSESwift.framework", "${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework", "${BUILT_PRODUCTS_DIR}/ReachabilitySwift/Reachability.framework", "${BUILT_PRODUCTS_DIR}/Realm/Realm.framework", "${BUILT_PRODUCTS_DIR}/RealmSwift/RealmSwift.framework", "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework", + "${BUILT_PRODUCTS_DIR}/SaltedgeAuthenticatorCore/SEAuthenticatorCore.framework", "${BUILT_PRODUCTS_DIR}/SaltedgeAuthenticatorSDK/SEAuthenticator.framework", + "${BUILT_PRODUCTS_DIR}/SaltedgeAuthenticatorSDKv2/SEAuthenticatorV2.framework", "${BUILT_PRODUCTS_DIR}/TinyConstraints/TinyConstraints.framework", "${BUILT_PRODUCTS_DIR}/Valet/Valet.framework", "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework", @@ -1718,12 +1832,15 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseInstallations.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleDataTransport.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleUtilities.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/JOSESwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBLPromises.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reachability.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RealmSwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SEAuthenticatorCore.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SEAuthenticator.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SEAuthenticatorV2.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TinyConstraints.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Valet.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework", @@ -1742,6 +1859,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 8434188C26BD33180027CF1B /* ConnectViewModel.swift in Sources */, 9DCED8F922CE18280050ED3C /* PasscodeSymbolView.swift in Sources */, 9D98746D24B3536400EE89BB /* ConsentSharedDataView.swift in Sources */, 9DE94108246D4229000029A2 /* AuthorizationsViewModel.swift in Sources */, @@ -1757,6 +1875,7 @@ 9DCED8F722CE18280050ED3C /* ModalView.swift in Sources */, 9DCED8E122CE18280050ED3C /* Localizations.swift in Sources */, 9DCED8DF22CE18280050ED3C /* AppSettings.swift in Sources */, + 9D63AB5A26721063007EE1C8 /* AuthorizationContentDynamicStackView.swift in Sources */, 959E30A0247BC78C0067354A /* LanguagePickerViewModel.swift in Sources */, 9DCED90C22CE18280050ED3C /* AuthorizationsCoordinator.swift in Sources */, 9DCED8FC22CE18280050ED3C /* CountdownProgressView.swift in Sources */, @@ -1787,7 +1906,7 @@ 9DCED8D322CE18280050ED3C /* StackViewExtensions.swift in Sources */, 95C8C0EE25F272CC005E787F /* LocationManager.swift in Sources */, 9DCED91522CE18280050ED3C /* Connection.swift in Sources */, - 9DD8945D22E9D969008F3130 /* ReachabilityManager.swift in Sources */, + 9DD8945D22E9D969008F3130 /* ConnectivityManager.swift in Sources */, 9D87F4A7242A6BCD00F85D1F /* ConnectionsViewModel.swift in Sources */, 9DCED8AB22CE18280050ED3C /* SetupAppViewController.swift in Sources */, 9D98747324B353C000EE89BB /* ConsentAccountView.swift in Sources */, @@ -1816,12 +1935,14 @@ 9DE56A0C24AB702A00FA5B2C /* ConsentsCoordinator.swift in Sources */, 9DCED90322CE18280050ED3C /* TaptileFeedbackButton.swift in Sources */, 9DCED8E022CE18280050ED3C /* UserDefaultsHelper.swift in Sources */, + 84726A0726B98538005C792E /* AddConnectionV2Fields.swift in Sources */, + 9DCC5E25265D1DD40054B933 /* ConnectionsInteractorV2.swift in Sources */, + 9DD155F326613CEA00AEFA0D /* BaseConnectionsInteractor.swift in Sources */, 9DCED8D522CE18280050ED3C /* ApplicationExtensions.swift in Sources */, 9DCED8D622CE18280050ED3C /* ViewExtensions+Animations.swift in Sources */, 9DCED89C22CE18280050ED3C /* SettingsViewController.swift in Sources */, 9DCED8D122CE18280050ED3C /* BundleExtensions.swift in Sources */, 95C1EFC522D5ECAE00078A28 /* AboutViewController.swift in Sources */, - 9DCED8EB22CE18280050ED3C /* ParametersSerializer.swift in Sources */, 9DCED8DC22CE18280050ED3C /* LocalizationHelper.swift in Sources */, 9DCED92622CE18280050ED3C /* AppDelegate.swift in Sources */, 9D98747024B353A300EE89BB /* ConsentExpirationView.swift in Sources */, @@ -1888,6 +2009,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 8434189226BD6FFB0027CF1B /* MockConnectable.swift in Sources */, 9D98747424B353C000EE89BB /* ConsentAccountView.swift in Sources */, 9D59DAF62490C11500117CD7 /* AuthorizationView.swift in Sources */, 959E30A3247BC7930067354A /* LanguagePickerCoordinator.swift in Sources */, @@ -1896,18 +2018,20 @@ 9DE324D022D39A9F00EB162A /* ModalView.swift in Sources */, 959E30B0247BDA250067354A /* PasscodeViewModelSpec.swift in Sources */, 9DE3249822D399EB00EB162A /* FontsExtensions.swift in Sources */, - 9DE324B322D39A3700EB162A /* ParametersSerializer.swift in Sources */, + 9D6907432657E67C00A9CE94 /* SEConnectionRouterSpecV2.swift in Sources */, 9DE324AF22D39A2D00EB162A /* PasscodeManger.swift in Sources */, 9581A9D92477D63C0066EF5E /* WKWebViewController.swift in Sources */, 9DF7964A2497C2D2001290DA /* AuthorizationContentView.swift in Sources */, 9D66E1E522D6146900BD59B6 /* HeadersSpec.swift in Sources */, 95F409032475620600727095 /* ConnectionsViewModel.swift in Sources */, 9DE3247122D398ED00EB162A /* SpecUtils.swift in Sources */, + 9DCC5E26265D1DD40054B933 /* ConnectionsInteractorV2.swift in Sources */, 9DE3248A22D399B800EB162A /* AuthorizationsDataSource.swift in Sources */, 9DE3247222D398ED00EB162A /* SpecCryptoHelper.swift in Sources */, 9DE324B622D39A4700EB162A /* SettingsViewController.swift in Sources */, 959E3098247BBA190067354A /* SettingsViewModel.swift in Sources */, 9DFD634923BF366C0060B29F /* AuthorizationStateView.swift in Sources */, + 9D6907402657E38A00A9CE94 /* RequestParametersBuilderSpecV2.swift in Sources */, 9DE324A522D39A1300EB162A /* Constants.swift in Sources */, 9D4310F622D7662C004A7FCF /* AboutViewController.swift in Sources */, 9D6575FB232A9E8100DCC53E /* DateUtilsSpec.swift in Sources */, @@ -1919,6 +2043,7 @@ 9DE324C222D39A6D00EB162A /* OnboardingViewController.swift in Sources */, 959E30AA247BD0170067354A /* LicensesViewModel.swift in Sources */, 959E30A6247BCBB90067354A /* LicensesCoordinator.swift in Sources */, + 9DD155F426613CEB00AEFA0D /* BaseConnectionsInteractor.swift in Sources */, 9DE324E022D39ACB00EB162A /* LoadingIndicator.swift in Sources */, 9DE3249D22D399F600EB162A /* StackViewExtensions.swift in Sources */, 9D77212A231E50400015BAC6 /* AuthorizationsHeaderCollectionViewCell.swift in Sources */, @@ -1929,6 +2054,7 @@ 9DE3247B22D3998200EB162A /* AuthorizationsCoordinator.swift in Sources */, 9DE308BC2446287A009EBD39 /* PasscodeViewController.swift in Sources */, 9D66E1A922D4C1C100BD59B6 /* AuthorizationDataSpec.swift in Sources */, + 9D6907452657F94C00A9CE94 /* SEAuthorizationRouterSpecV2.swift in Sources */, 9DE324DC22D39AC100EB162A /* TaptileFeedbackButton.swift in Sources */, 9D453CC223CCB56400702695 /* main.swift in Sources */, 9DE324AB22D39A2300EB162A /* Localizations.swift in Sources */, @@ -1950,15 +2076,18 @@ 9DE324A022D399FE00EB162A /* ViewExtensions+Animations.swift in Sources */, 9D8E5BA22445A1E70081E60E /* OnboardingViewModelSpec.swift in Sources */, 9DE308BF24474D92009EBD39 /* PasscodeViewModel.swift in Sources */, - 9DD8945E22E9D969008F3130 /* ReachabilityManager.swift in Sources */, + 9DD8945E22E9D969008F3130 /* ConnectivityManager.swift in Sources */, 9DD8946122E9F4BD008F3130 /* NotificationsHelper.swift in Sources */, 9DF7964D2498EFC6001290DA /* SingleAuthorizationViewModel.swift in Sources */, 9DE3249B22D399F200EB162A /* BundleExtensions.swift in Sources */, 9D94095B23618CF00080EBB5 /* CacheHelper.swift in Sources */, 9DE3249022D399D200EB162A /* BiometricsPresenter.swift in Sources */, 9DD4DF2923759767000A9B80 /* AVCaptureHelper.swift in Sources */, + 9D69071C2655439200A9CE94 /* JWSHelperSpec.swift in Sources */, 9DE3249922D399ED00EB162A /* StringExtensions.swift in Sources */, + 8432878826CE378400F77DD5 /* AuthorizationStatusSpec.swift in Sources */, 9D66E1DE22D6146900BD59B6 /* ConnectionRouterSpec.swift in Sources */, + 8480F53B26BC182200FB5DFF /* ConnectionsViewModelSpec.swift in Sources */, 9DE324CE22D39A9800EB162A /* CustomSpacingLabel.swift in Sources */, 9DE569FF24AA171400FA5B2C /* ConsentsViewController.swift in Sources */, 9D5AC68B23571958003B00E2 /* LicensesViewController.swift in Sources */, @@ -1980,13 +2109,16 @@ 9D6575F8232A9B0200DCC53E /* DateExtensionsSpec.swift in Sources */, 9DE3249E22D399F900EB162A /* DicitionaryExtensions.swift in Sources */, 9DE3247422D398ED00EB162A /* BaseSpec.swift in Sources */, + 9D63AB5B26721063007EE1C8 /* AuthorizationContentDynamicStackView.swift in Sources */, 95D784E723043949002BF219 /* RealmMigrationManager.swift in Sources */, 9DE3249A22D399EF00EB162A /* ColorExtensions.swift in Sources */, 9DE56A0624AA223B00FA5B2C /* ConsentCell.swift in Sources */, 9DA1C81524ADDF9F0024CDBA /* ConsentDetailViewController.swift in Sources */, 959E30B2247BDA250067354A /* LanguagePickerViewModelSpec.swift in Sources */, + 9D63AB5826715B4C007EE1C8 /* StringExtensionsSpec.swift in Sources */, 9D98746E24B3536400EE89BB /* ConsentSharedDataView.swift in Sources */, 9D1DEE7523D9C5D6003C79AC /* ConnectionPickerViewController.swift in Sources */, + 8434189026BD41A20027CF1B /* ConnectViewModelSpec.swift in Sources */, 9DBAD7A0247E55BD0023F944 /* QRCodeCoordinator.swift in Sources */, 9DE3245A22D3986B00EB162A /* RevokeConnectionResponseSpec.swift in Sources */, 9D772133231E51E40015BAC6 /* AuthorizationCollectionViewCell.swift in Sources */, @@ -1995,6 +2127,7 @@ 959E30B1247BDA250067354A /* SettingsViewModelSpec.swift in Sources */, 9DE3247322D398ED00EB162A /* URLRequestBuilder.swift in Sources */, 9DE3247522D398ED00EB162A /* DataFixtures.swift in Sources */, + 9D63AB5D2672123A007EE1C8 /* StackViewExtensionsSpec.swift in Sources */, 9D66E1DF22D6146900BD59B6 /* AuthorizationRouterSpec.swift in Sources */, 9DE324D222D39AA700EB162A /* PasscodeSymbolView.swift in Sources */, 9D98747D24B390F200EE89BB /* ConsentsInteractor.swift in Sources */, @@ -2031,6 +2164,7 @@ 9DE3249722D399E800EB162A /* ViewsExtensions.swift in Sources */, 9DE3249522D399E300EB162A /* ViewControllerExtensions.swift in Sources */, 95D784E82304399B002BF219 /* AddConnectionSupportEmail.swift in Sources */, + 84726A0826B98538005C792E /* AddConnectionV2Fields.swift in Sources */, 959E30B4247BE56C0067354A /* SettingCellModel.swift in Sources */, 9D66E1AB22D4C1CA00BD59B6 /* CryptoErrorsSpec.swift in Sources */, 9D630BCA243DC464000A9ACC /* OnboardingViewModel.swift in Sources */, @@ -2053,6 +2187,7 @@ 9DE324BE22D39A5F00EB162A /* AuthorizationsViewController.swift in Sources */, 9D5BCFDD247FF595009A6B40 /* InstantActionHandler.swift in Sources */, 9DCBB28C243618B900DB3F85 /* Observable.swift in Sources */, + 8434188D26BD33180027CF1B /* ConnectViewModel.swift in Sources */, 9DE3247622D3997200EB162A /* ApplicationCoordinator.swift in Sources */, 9DB18ED722D4A35500BEF299 /* Connection.swift in Sources */, 9D66E1AD22D4C1D100BD59B6 /* ConnectionSpec.swift in Sources */, @@ -2062,6 +2197,7 @@ 959E309F247BC7880067354A /* LanguagePickerViewController.swift in Sources */, 9DE3248D22D399C200EB162A /* ConnectionRepository.swift in Sources */, 9D66E1AF22D4C1D700BD59B6 /* ConnectionDataSpec.swift in Sources */, + 9DD155EC265E74C800AEFA0D /* ApiVersionExtractorSpec.swift in Sources */, 9DE324E122D39AD000EB162A /* AppDelegate.swift in Sources */, 9D1DEE8123DB5177003C79AC /* ActionRouterSpec.swift in Sources */, ); @@ -2279,7 +2415,9 @@ isa = XCBuildConfiguration; baseConfigurationReference = 5A77B827BD85D2BAD4D1123E /* Pods-Authenticator_Tests.debug.xcconfig */; buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; DEVELOPMENT_TEAM = 8QFQG87EDC; + "EXCLUDED_ARCHS[sdk=*]" = arm64; FRAMEWORK_SEARCH_PATHS = ( "$(PLATFORM_DIR)/Developer/Library/Frameworks", "$(inherited)", @@ -2303,7 +2441,9 @@ isa = XCBuildConfiguration; baseConfigurationReference = B108DBE02AD4CBDC3AD30A22 /* Pods-Authenticator_Tests.release.xcconfig */; buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; DEVELOPMENT_TEAM = 8QFQG87EDC; + "EXCLUDED_ARCHS[sdk=*]" = arm64; FRAMEWORK_SEARCH_PATHS = ( "$(PLATFORM_DIR)/Developer/Library/Frameworks", "$(inherited)", @@ -2335,6 +2475,7 @@ CODE_SIGN_STYLE = Automatic; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 8QFQG87EDC; + "EXCLUDED_ARCHS[sdk=*]" = arm64; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "TestHost-iOS/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; @@ -2364,6 +2505,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = 8QFQG87EDC; + "EXCLUDED_ARCHS[sdk=*]" = arm64; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "TestHost-iOS/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; diff --git a/Example/Authenticator/Assets.xcassets/Colors/descriptionYellow.colorset/Contents.json b/Example/Authenticator/Assets.xcassets/Colors/descriptionYellow.colorset/Contents.json new file mode 100644 index 00000000..43c016c1 --- /dev/null +++ b/Example/Authenticator/Assets.xcassets/Colors/descriptionYellow.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.180", + "green" : "0.750", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/Authenticator/Assets.xcassets/Colors/extraTextColor.colorset/Contents.json b/Example/Authenticator/Assets.xcassets/Colors/extraTextColor.colorset/Contents.json new file mode 100644 index 00000000..9a82cf52 --- /dev/null +++ b/Example/Authenticator/Assets.xcassets/Colors/extraTextColor.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.729", + "green" : "0.635", + "red" : "0.506" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/Authenticator/Base.lproj/LaunchScreen.storyboard b/Example/Authenticator/Base.lproj/LaunchScreen.storyboard index 0b81ebeb..05c70f55 100644 --- a/Example/Authenticator/Base.lproj/LaunchScreen.storyboard +++ b/Example/Authenticator/Base.lproj/LaunchScreen.storyboard @@ -1,9 +1,9 @@ - + - + diff --git a/Example/Authenticator/Coordinators/AuthorizationsCoordinator.swift b/Example/Authenticator/Coordinators/AuthorizationsCoordinator.swift index c89b07d6..99fd44d0 100644 --- a/Example/Authenticator/Coordinators/AuthorizationsCoordinator.swift +++ b/Example/Authenticator/Coordinators/AuthorizationsCoordinator.swift @@ -25,7 +25,7 @@ import SEAuthenticator final class AuthorizationsCoordinator: Coordinator { let rootViewController = AuthorizationsViewController() - private let dataSource = AuthorizationsDataSource(locationManagement: LocationManager.shared) + private let dataSource = AuthorizationsDataSource() private let viewModel = AuthorizationsViewModel() private var qrCoordinator: QRCodeCoordinator? @@ -95,4 +95,8 @@ extension AuthorizationsCoordinator: AuthorizationsViewControllerDelegate { rootViewController.present(actionSheet, animated: true) } + + func requestLocation() { + LocationManager.shared.requestLocationAuthorization() + } } diff --git a/Example/Authenticator/Coordinators/Connect/InstantActionCoordinator.swift b/Example/Authenticator/Coordinators/Connect/InstantActionCoordinator.swift index c05d0761..4791daec 100644 --- a/Example/Authenticator/Coordinators/Connect/InstantActionCoordinator.swift +++ b/Example/Authenticator/Coordinators/Connect/InstantActionCoordinator.swift @@ -22,6 +22,7 @@ import UIKit import SEAuthenticator +import SEAuthenticatorCore final class InstantActionCoordinator: Coordinator { private var rootViewController: UIViewController @@ -29,10 +30,10 @@ final class InstantActionCoordinator: Coordinator { private var instantActionHandler: InstantActionHandler private var qrCodeCoordinator: QRCodeCoordinator? - init(rootViewController: UIViewController, qrUrl: URL, actionGuid: GUID, connectUrl: URL) { + init(rootViewController: UIViewController, qrUrl: URL) { self.rootViewController = rootViewController self.connectViewController = ConnectViewController() - self.instantActionHandler = InstantActionHandler(qrUrl: qrUrl, actionGuid: actionGuid, connectUrl: connectUrl) + self.instantActionHandler = InstantActionHandler(qrUrl: qrUrl) if #available(iOS 13.0, *) { connectViewController.isModalInPresentation = true } @@ -64,10 +65,10 @@ extension InstantActionCoordinator: InstantActionEventsDelegate { } connectViewController.add(pickerViewController) - pickerViewModel.selectedConnectionClosure = { guid, accessToken in + pickerViewModel.selectedConnectionClosure = { actionData in pickerViewController.remove() self.connectViewController.title = l10n(.newAction) - self.instantActionHandler.submitAction(for: guid, accessToken: accessToken) + self.instantActionHandler.submitAction(for: actionData) } } diff --git a/Example/Authenticator/Coordinators/Connect/QRCodeCoordinator.swift b/Example/Authenticator/Coordinators/Connect/QRCodeCoordinator.swift index 6d5cc723..c0b0a9b1 100644 --- a/Example/Authenticator/Coordinators/Connect/QRCodeCoordinator.swift +++ b/Example/Authenticator/Coordinators/Connect/QRCodeCoordinator.swift @@ -21,7 +21,7 @@ // import UIKit -import SEAuthenticator +import SEAuthenticatorCore final class QRCodeCoordinator: Coordinator { private var rootViewController: UIViewController @@ -29,15 +29,12 @@ final class QRCodeCoordinator: Coordinator { private var connectViewCoordinator: ConnectViewCoordinator? private var instantActionCoordinator: InstantActionCoordinator? - var dismissClosure: (() -> ())? - init(rootViewController: UIViewController) { self.rootViewController = rootViewController self.qrCodeViewController = QRCodeViewController() } func start() { - dismissClosure = qrCodeViewController.shouldDismissClosure qrCodeViewController.delegate = self if let navController = rootViewController.navigationController { navController.present(qrCodeViewController, animated: true) @@ -55,13 +52,10 @@ extension QRCodeCoordinator: QRCodeViewControllerDelegate { guard let url = URL(string: data), SEConnectHelper.isValid(deepLinkUrl: url) else { return } - if let actionGuid = SEConnectHelper.actionGuid(from: url), - let connectUrl = SEConnectHelper.connectUrl(from: url) { + if SEConnectHelper.shouldStartInstantActionFlow(url: url) { instantActionCoordinator = InstantActionCoordinator( rootViewController: rootViewController, - qrUrl: url, - actionGuid: actionGuid, - connectUrl: connectUrl + qrUrl: url ) instantActionCoordinator?.start() } else { diff --git a/Example/Authenticator/Coordinators/ConnectViewCoordinator.swift b/Example/Authenticator/Coordinators/ConnectViewCoordinator.swift index 5b301f71..3f54fabf 100644 --- a/Example/Authenticator/Coordinators/ConnectViewCoordinator.swift +++ b/Example/Authenticator/Coordinators/ConnectViewCoordinator.swift @@ -22,6 +22,7 @@ import UIKit import SEAuthenticator +import SEAuthenticatorCore enum ConnectionType: Equatable { case newConnection(String) @@ -42,7 +43,7 @@ final class ConnectViewCoordinator: Coordinator { private let rootViewController: UIViewController private lazy var webViewController = ConnectorWebViewController() - private var connectViewController = ConnectViewController() + private lazy var connectViewController = ConnectViewController() private var qrCodeCoordinator: QRCodeCoordinator? private var connectHandler: ConnectHandler? @@ -86,7 +87,17 @@ extension ConnectViewCoordinator: ConnectEventsDelegate { } func requestLocationAuthorization() { - LocationManager.shared.requestLocationAuthorization() + if LocationManager.shared.notDeterminedAuthorization { + LocationManager.shared.requestLocationAuthorization() + } else { + connectViewController.showInfoAlert( + withTitle: l10n(.turnOnLocationServices), + message: l10n(.turnOnPhoneLocationServicesDescription), + completion: { + LocationManager.shared.requestLocationAuthorization() + } + ) + } } func startWebViewLoading(with connectUrlString: String) { diff --git a/Example/Authenticator/Coordinators/Connections/ConnectionsCoordinator.swift b/Example/Authenticator/Coordinators/Connections/ConnectionsCoordinator.swift index 4e9e7770..3f9e8948 100644 --- a/Example/Authenticator/Coordinators/Connections/ConnectionsCoordinator.swift +++ b/Example/Authenticator/Coordinators/Connections/ConnectionsCoordinator.swift @@ -21,7 +21,7 @@ // import UIKit -import SEAuthenticator +import SEAuthenticatorCore final class ConnectionsCoordinator: Coordinator { private var rootViewController: UIViewController @@ -29,7 +29,7 @@ final class ConnectionsCoordinator: Coordinator { private var connectViewCoordinator: ConnectViewCoordinator? private var qrCodeCoordinator: QRCodeCoordinator? private var consentsCoordinator: ConsentsCoordinator? - private var viewModel = ConnectionsViewModel() + private var viewModel = ConnectionsViewModel(reachabilityManager: ConnectivityManager.shared) init(rootViewController: UIViewController) { self.rootViewController = rootViewController @@ -48,9 +48,29 @@ final class ConnectionsCoordinator: Coordinator { // MARK: - ConnectionsListEventsDelegate extension ConnectionsCoordinator: ConnectionsEventsDelegate { + func showNoInternetConnectionAlert(completion: @escaping () -> Void) { + currentViewController.showConfirmationAlert( + withTitle: l10n(.noInternetConnection), + message: l10n(.pleaseCheckAndTryAgain), + confirmActionTitle: l10n(.retry), + confirmAction: { _ in completion() } + ) + } + + func showDeleteConfirmationAlert(completion: @escaping () -> Void) { + currentViewController.showConfirmationAlert( + withTitle: l10n(.deleteConnection), + message: l10n(.deleteConnectionDescription), + confirmActionTitle: l10n(.delete), + confirmActionStyle: .destructive, + cancelTitle: l10n(.cancel), + confirmAction: { _ in completion() } + ) + } + func addPressed() { guard AVCaptureHelper.cameraIsAuthorized() else { - self.currentViewController.showConfirmationAlert( + currentViewController.showConfirmationAlert( withTitle: l10n(.deniedCamera), message: l10n(.deniedCameraDescription), confirmActionTitle: l10n(.goToSettings), @@ -112,4 +132,8 @@ extension ConnectionsCoordinator: ConnectionsEventsDelegate { connectViewCoordinator = ConnectViewCoordinator(rootViewController: currentViewController, connectionType: .reconnect(id)) connectViewCoordinator?.start() } + + func presentError(_ error: String) { + currentViewController.present(message: error) + } } diff --git a/Example/Authenticator/Coordinators/Connections/ConsentDetailCoordinator.swift b/Example/Authenticator/Coordinators/Connections/ConsentDetailCoordinator.swift index 4594363e..11b013a1 100644 --- a/Example/Authenticator/Coordinators/Connections/ConsentDetailCoordinator.swift +++ b/Example/Authenticator/Coordinators/Connections/ConsentDetailCoordinator.swift @@ -21,7 +21,7 @@ // import UIKit -import SEAuthenticator +import SEAuthenticatorCore final class ConsentDetailCoordinator: Coordinator { private var rootViewController: UIViewController diff --git a/Example/Authenticator/Coordinators/Main/ApplicationCoordinator.swift b/Example/Authenticator/Coordinators/Main/ApplicationCoordinator.swift index 866dc8dc..deec350b 100644 --- a/Example/Authenticator/Coordinators/Main/ApplicationCoordinator.swift +++ b/Example/Authenticator/Coordinators/Main/ApplicationCoordinator.swift @@ -21,7 +21,7 @@ // import UIKit -import SEAuthenticator +import SEAuthenticatorCore final class ApplicationCoordinator: Coordinator { private let window: UIWindow? @@ -236,13 +236,10 @@ final class ApplicationCoordinator: Coordinator { } private func startConnect(url: URL, controller: UIViewController) { - if let actionGuid = SEConnectHelper.actionGuid(from: url), - let connectUrl = SEConnectHelper.connectUrl(from: url) { + if SEConnectHelper.shouldStartInstantActionFlow(url: url) { instantActionCoordinator = InstantActionCoordinator( rootViewController: controller, - qrUrl: url, - actionGuid: actionGuid, - connectUrl: connectUrl + qrUrl: url ) instantActionCoordinator?.start() } else { diff --git a/Example/Authenticator/Database/Migrations/AddConnectionV2Fields.swift b/Example/Authenticator/Database/Migrations/AddConnectionV2Fields.swift new file mode 100644 index 00000000..281b8ae0 --- /dev/null +++ b/Example/Authenticator/Database/Migrations/AddConnectionV2Fields.swift @@ -0,0 +1,39 @@ +// +// AddConnectionV2Fields +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import RealmSwift + +struct AddConnectionV2Fields: RealmMigratable { + static func execute(_ migration: Migration) { + migration.enumerateObjects(ofType: Connection.className()) { (_, newObject) in + guard let object = newObject else { return } + + let apiVersionPath = #keyPath(Connection.apiVersion) + object[apiVersionPath] = "" + let providerIdPath = #keyPath(Connection.providerId) + object[providerIdPath] = "" + let publicKeyPath = #keyPath(Connection.publicKey) + object[publicKeyPath] = "" + } + } +} diff --git a/Example/Authenticator/Database/RealmMigrationManager.swift b/Example/Authenticator/Database/RealmMigrationManager.swift index 7bc7760c..af363dbf 100644 --- a/Example/Authenticator/Database/RealmMigrationManager.swift +++ b/Example/Authenticator/Database/RealmMigrationManager.swift @@ -29,7 +29,8 @@ protocol RealmMigratable { private let availableMigrations: [RealmMigratable.Type] = [ AddConnectionSupportEmail.self, - AddConnectionGeolocation.self + AddConnectionGeolocation.self, + AddConnectionV2Fields.self ] struct RealmMigrationManager { diff --git a/Example/Authenticator/Models/Collectors/ConnectionsCollector.swift b/Example/Authenticator/Models/Collectors/ConnectionsCollector.swift index c85a6c14..46394533 100644 --- a/Example/Authenticator/Models/Collectors/ConnectionsCollector.swift +++ b/Example/Authenticator/Models/Collectors/ConnectionsCollector.swift @@ -45,6 +45,10 @@ struct ConnectionsCollector { return Array(activeConnections).filter { $0.baseUrl == connectUrl } } + static func activeConnections(by providerId: String) -> [Connection] { + return Array(activeConnections).filter { $0.providerId == providerId } + } + static var connectionNames: [String] { return allConnections.map { $0.name } } diff --git a/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift b/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift index 3a3dbd7d..c1e7c869 100644 --- a/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift +++ b/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift @@ -22,27 +22,27 @@ import UIKit import SEAuthenticator +import SEAuthenticatorV2 +import SEAuthenticatorCore final class AuthorizationsDataSource { - private var authorizationResponses = [SEAuthorizationData]() private var viewModels = [AuthorizationDetailViewModel]() - private var locationManagement: LocationManagement - init(locationManagement: LocationManagement) { - self.locationManagement = locationManagement - } + func update(with baseData: [SEBaseAuthorizationData]) -> Bool { + let viewModelsV1 = baseData + .filter { $0.apiVersion == API_V1_VERSION } + .toAuthorizationViewModels(existedViewModels: viewModels) + .merge(array: self.viewModels.filter { $0.apiVersion == API_V1_VERSION }) - func update(with authorizationResponses: [SEAuthorizationData]) -> Bool { - if authorizationResponses != self.authorizationResponses { - self.authorizationResponses = authorizationResponses - self.viewModels = authorizationResponses.compactMap { response in - guard response.expiresAt >= Date() else { return nil } + let viewModelsV2 = baseData + .filter { $0.apiVersion == API_V2_VERSION } + .toAuthorizationViewModels(existedViewModels: viewModels) + .merge(array: self.viewModels.filter { $0.apiVersion == API_V2_VERSION }) - let connection = ConnectionsCollector.with(id: response.connectionId) - let showLocationWarning = locationManagement.showLocationWarning(connection: connection) + let allViewModels = (viewModelsV1 + viewModelsV2).sorted(by: { $0.createdAt < $1.createdAt }) - return AuthorizationDetailViewModel(response, showLocationWarning: showLocationWarning) - }.merge(array: self.viewModels).sorted(by: { $0.createdAt < $1.createdAt }) + if allViewModels != self.viewModels { + self.viewModels = allViewModels return true } @@ -85,12 +85,12 @@ final class AuthorizationsDataSource { return viewModels.firstIndex(of: viewModel) } - func viewModel(with authorizationId: String) -> AuthorizationDetailViewModel? { - return viewModels.filter({ $0.authorizationId == authorizationId }).first + func viewModel(with authorizationId: String, apiVersion: ApiVersion) -> AuthorizationDetailViewModel? { + return viewModels.filter { $0.authorizationId == authorizationId && $0.apiVersion == apiVersion}.first } - func confirmationData(for authorizationId: String) -> SEConfirmAuthorizationRequestData? { - guard let viewModel = viewModel(with: authorizationId), + func confirmationData(for authorizationId: String, apiVersion: ApiVersion) -> SEConfirmAuthorizationRequestData? { + guard let viewModel = viewModel(with: authorizationId, apiVersion: apiVersion), let connection = ConnectionsCollector.with(id: viewModel.connectionId), let url = connection.baseUrl else { return nil } @@ -107,7 +107,6 @@ final class AuthorizationsDataSource { } func clearAuthorizations() { - authorizationResponses.removeAll() viewModels.removeAll() } @@ -128,22 +127,57 @@ final class AuthorizationsDataSource { } } -private extension Array where Element == AuthorizationDetailViewModel { - func merge(array: [Element]) -> [AuthorizationDetailViewModel] { - let finalElements: [Element] = array.compactMap { element in - if element.expired || element.state.value != .base { - return element +private extension Array where Element == SEBaseAuthorizationData { + /* + Converts SEBaseAuthorizationData to array of AuthorizationDetailViewModel's + + - parameters: + - existedViewModels: The array of already existed authorization view models + + For API v2 at first it checks if status of received authorization response is final, + than it filters existed view models to find the one with the same authorization id. + + For the found view model sets the final state (if it doesn't have the final status already) and return it. + + For API v1 it only checks if this authorization isn't already expired + and pass the received response to AuthorizationDetailViewModel. + */ + func toAuthorizationViewModels( + existedViewModels: [AuthorizationDetailViewModel] + ) -> [AuthorizationDetailViewModel] { + return compactMap { response in + if let v2Response = response as? SEAuthorizationDataV2, + v2Response.status.isFinal, + let filtered = existedViewModels.filter({ $0.authorizationId == v2Response.id }).first { + guard filtered.authorizationExpiresAt >= Date(), + let filteredStatus = filtered.status, !filteredStatus.isFinal, !filteredStatus.isClosed else { return nil } + + filtered.setFinal(status: v2Response.status) + return filtered } else { - return nil + guard response.expiresAt >= Date() else { return nil } + + return AuthorizationDetailViewModel(response, apiVersion: response.apiVersion) } } + } +} + +private extension Array where Element == AuthorizationDetailViewModel { + func merge(array: [Element]) -> [AuthorizationDetailViewModel] { + let finalElements: [Element] = array.filter { element in + return element.expired || element.state.value != .base + } let newAuthIds: [String] = self.map { $0.authorizationId } let newConnectionIds: [String] = self.map { $0.connectionId } var merged: [Element] = self - merged.append(contentsOf: finalElements - .filter { !newAuthIds.contains($0.authorizationId) || !newConnectionIds.contains($0.connectionId) } + merged.append( + contentsOf: finalElements.filter { + !newAuthIds.contains($0.authorizationId) || + !newConnectionIds.contains($0.connectionId) + } ) return merged diff --git a/Example/Authenticator/Models/Database/Connection.swift b/Example/Authenticator/Models/Database/Connection.swift index 1fe875bb..b2663a25 100644 --- a/Example/Authenticator/Models/Database/Connection.swift +++ b/Example/Authenticator/Models/Database/Connection.swift @@ -28,7 +28,7 @@ enum ConnectionStatus: String { case inactive } -@objcMembers final class Connection: Object { +@objcMembers final class Connection: Object, Decodable { dynamic var id: String = "" dynamic var guid: String = UUID().uuidString dynamic var name: String = "" @@ -38,13 +38,62 @@ enum ConnectionStatus: String { dynamic var accessToken: String = "" dynamic var status: String = ConnectionStatus.inactive.rawValue dynamic var supportEmail: String = "" - dynamic let geolocationRequired = RealmOptional() + dynamic var geolocationRequired = RealmOptional() dynamic var createdAt: Date = Date() dynamic var updatedAt: Date = Date() + dynamic var providerId: String? + dynamic var publicKey: String = "" + dynamic var apiVersion: String = "1" override static func primaryKey() -> String? { return #keyPath(Connection.guid) } + + enum CodingKeys: String, CodingKey { + case id + case guid + case name + case code + case baseUrlString = "connect_url" + case logoUrlString = "provier_logo_url" + case accessToken = "access_token" + case status + case supportEmail = "support_email" + case geolocationRequired = "geolocation_required" + case createdAt = "created_at" + case updatedAt = "updated_at" + case providerId = "provider_id" + case publicKey = "public_key" + case apiVersion = "api_version" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + name = try container.decode(String.self, forKey: .name) + code = try container.decode(String.self, forKey: .code) + baseUrlString = try container.decode(String.self, forKey: .baseUrlString) + logoUrlString = try container.decode(String.self, forKey: .logoUrlString) + accessToken = try container.decode(String.self, forKey: .accessToken) + status = try container.decode(String.self, forKey: .status) + supportEmail = try container.decode(String.self, forKey: .supportEmail) + let boolResult = try decoder.singleValueContainer() + if boolResult.decodeNil() == false { + let value = try boolResult.decode(Bool.self) + geolocationRequired = RealmOptional(value) + } + providerId = try container.decode(String.self, forKey: .providerId) + publicKey = try container.decode(String.self, forKey: .publicKey) + apiVersion = try container.decode(String.self, forKey: .apiVersion) + id = try container.decode(String.self, forKey: .id) + guid = try container.decode(String.self, forKey: .guid) + createdAt = try container.decode(String.self, forKey: .createdAt).iso8601date ?? Date() + updatedAt = try container.decode(String.self, forKey: .updatedAt).iso8601date ?? Date() + super.init() + } + + required init() { + super.init() + } } extension Connection { @@ -59,4 +108,12 @@ extension Connection { var isManaged: Bool { return realm != nil } + + var isApiV2: Bool { + return apiVersion == "2" + } + + var providerPublicKeyTag: String { + return "\(guid)_provider_public_key" + } } diff --git a/Example/Authenticator/Models/Repositories/ConnectionRepository.swift b/Example/Authenticator/Models/Repositories/ConnectionRepository.swift index 7edee071..9a58ecdc 100644 --- a/Example/Authenticator/Models/Repositories/ConnectionRepository.swift +++ b/Example/Authenticator/Models/Repositories/ConnectionRepository.swift @@ -22,19 +22,25 @@ import Foundation import RealmSwift +import SEAuthenticatorCore struct ConnectionRepository { @discardableResult static func setAccessTokenAndActive(_ connection: Connection, accessToken token: String) -> Bool { + guard let decryptedTokenData = try? SECryptoHelper.decrypt(key: token, tag: SETagHelper.create(for: connection.guid)).json, + let decryptedAccessToken = decryptedTokenData[SENetKeys.accessToken] as? String else { + return false + } + var result = false if connection.isManaged { try? RealmManager.performRealmWriteTransaction { - connection.accessToken = token + connection.accessToken = decryptedAccessToken connection.status = ConnectionStatus.active.rawValue result = true } } else { - connection.accessToken = token + connection.accessToken = decryptedAccessToken connection.status = ConnectionStatus.active.rawValue result = true } diff --git a/Example/Authenticator/Supporting Files/AppDelegate.swift b/Example/Authenticator/Supporting Files/AppDelegate.swift index 839bd2bb..c1199dd5 100644 --- a/Example/Authenticator/Supporting Files/AppDelegate.swift +++ b/Example/Authenticator/Supporting Files/AppDelegate.swift @@ -22,6 +22,7 @@ import UIKit import UserNotifications import Firebase import SEAuthenticator +import SEAuthenticatorCore class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate { var window: UIWindow? @@ -32,7 +33,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { self.window = UIWindow(frame: UIScreen.main.bounds) UNUserNotificationCenter.current().delegate = self - ReachabilityManager.shared.observeReachability() + ConnectivityManager.shared.observeReachability() AppearanceHelper.setup() CacheHelper.setDefaultDiskAge() configureFirebase() @@ -160,9 +161,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD let userInfo = request.content.userInfo guard let apsDict = userInfo[SENetKeys.aps] as? [String: Any], - let dataDict = apsDict[SENetKeys.data] as? [String: Any], - let connectionId = dataDict[SENetKeys.connectionId] as? String, - let authorizationId = dataDict[SENetKeys.authorizationId] as? String else { return nil } - return (connectionId, authorizationId) + let dataDict = apsDict[SENetKeys.data] as? [String: Any] else { return nil } + + // NOTE: connection_id and authorization_id from v1 are strings, from v2 - ints + if let connectionId = dataDict[SENetKeys.connectionId] as? String, + let authorizationId = dataDict[SENetKeys.authorizationId] as? String { + return (connectionId, authorizationId) + } else if let connectionId = dataDict[SENetKeys.connectionId] as? Int, + let authorizationId = dataDict[SENetKeys.authorizationId] as? Int { + return ("\(connectionId)", "\(authorizationId)") + } else { + return nil + } } } diff --git a/Example/Authenticator/Supporting Files/Localization/en.lproj/Authenticator.strings b/Example/Authenticator/Supporting Files/Localization/en.lproj/Authenticator.strings index 4bfa1598..8639255c 100644 --- a/Example/Authenticator/Supporting Files/Localization/en.lproj/Authenticator.strings +++ b/Example/Authenticator/Supporting Files/Localization/en.lproj/Authenticator.strings @@ -23,6 +23,7 @@ "actions.proceed" = "Proceed"; "action.connect_provider" = "Connect Provider"; "actions.connect" = "Connect"; +"actions.view_id" = "ID:"; "actions.reconnect" = "Reconnect"; "actions.remove" = "Remove"; "actions.rename" = "Rename"; @@ -39,6 +40,7 @@ "actions.view_settings" = "View settings"; "actions.processing" = "Processing"; "actions.processing.description" = "This may take some time"; +"actions.access_to_location" = "Access to location"; "authorization.screen.name" = "Authenticator"; "authorization.success.title" = "Success"; "authorization.time_out.title" = "Time Out"; @@ -57,20 +59,28 @@ "errors.contact_support" = "Something went wrong"; "errors.denied_camera" = "Camera access denied"; "errors.denied_camera_description" = "Please allow camera usage in phone settings. Without camera you won't be able to connect provider."; +"errors.turn_on_location_services" = "Turn on Location Services"; +"errors.turn_on_location_sharing_description" = "Select Location and tap “While Using the App” to allow Authenticator to determine your location, as requested by your service provider."; +"errors.turn_on_phone_location_services" = "Salt Edge Authenticator requires Location Services. To turn on Location Services, open the Settings app > select Privacy > select Location Services > enable Location Services"; +"errors.access_to_location_services" = "Access to Location Services"; "errors.inactive_connection" = "Inactive. Please reconnect."; "errors.no_active_connections" = "You have no currently connected providers."; "errors.no_suitable_connection" = "You have no suitable connection for this action"; "errors.no_internet_connection" = "No Internet connection."; "errors.no_internet_connection_try_again" = "Please try again later."; +"errors.no_internet_connection_check_and_try_again" = "Please check your internet connection and try again"; "errors.passcode_dont_match" = "Passcodes don't match"; "errors.wrong_passcode" = "Wrong passcode"; "errors.passcode_ios_singular" = "You entered the wrong Passcode. Please try again in %{count} minute."; "errors.try_again" = "Please try again"; "errors.warning" = "Warning"; +"warnings.grant_access_to_location_services" = "Grant access to Location Services"; "warnings.inactivity_block_message" = "Application will be locked due to inactivity"; "in_app.connections_list.delete_connection" = "Are you sure you want to delete connection to your provider? This action cannot be undone."; -"in_app.authorizations.no_authorizations" = "Take an action"; -"in_app.authorizations.no_authorizations_description" = "You don’t have any active authentication requests yet. Scan QR code for instant action or to connect new provider."; +"in_app.authorizations.ip_address" = "IP address"; +"in_app.authorizations.authorization_expired" = "Authorization expired"; +"in_app.authorizations.no_authorizations" = "Nothing to authorize"; +"in_app.authorizations.no_authorizations_description" = "You don’t have any active authorization requests yet. Scan QR code for instant action or to connect new provider."; "in_app.connection.connected_on" = "Linked on"; "in_app.connection.active_consents" = "Active consents"; "in_app.connection.consents_singular" = "consent"; diff --git a/Example/Authenticator/Utils/Components Styles/LabelStyles.swift b/Example/Authenticator/Utils/Components Styles/LabelStyles.swift index 0102148c..46552b2f 100644 --- a/Example/Authenticator/Utils/Components Styles/LabelStyles.swift +++ b/Example/Authenticator/Utils/Components Styles/LabelStyles.swift @@ -30,5 +30,6 @@ extension UILabel { self.textAlignment = alignment self.numberOfLines = 0 self.lineBreakMode = .byWordWrapping + self.sizeToFit() } } diff --git a/Example/Authenticator/Utils/Extensions/ColorExtensions.swift b/Example/Authenticator/Utils/Extensions/ColorExtensions.swift index 21d47321..fb3cae79 100644 --- a/Example/Authenticator/Utils/Extensions/ColorExtensions.swift +++ b/Example/Authenticator/Utils/Extensions/ColorExtensions.swift @@ -71,6 +71,10 @@ extension UIColor { return UIColor(named: "dark80_grey100", in: .authenticator_main, compatibleWith: nil)! } + static var extraTextColor: UIColor { + return UIColor(named: "extraTextColor", in: .authenticator_main, compatibleWith: nil)! + } + static var white_dark100: UIColor { return UIColor(named: "white_dark100", in: .authenticator_main, compatibleWith: nil)! } @@ -86,4 +90,8 @@ extension UIColor { static var shadow: UIColor { return UIColor(named: "shadow", in: .authenticator_main, compatibleWith: nil)! } + + static var descriptionYellow: UIColor { + return UIColor(named: "descriptionYellow", in: .authenticator_main, compatibleWith: nil)! + } } diff --git a/Example/Authenticator/Utils/Extensions/SEEncryptedDataExtensions.swift b/Example/Authenticator/Utils/Extensions/SEEncryptedDataExtensions.swift index a82c7c79..088d657e 100644 --- a/Example/Authenticator/Utils/Extensions/SEEncryptedDataExtensions.swift +++ b/Example/Authenticator/Utils/Extensions/SEEncryptedDataExtensions.swift @@ -22,8 +22,10 @@ import Foundation import SEAuthenticator +import SEAuthenticatorV2 +import SEAuthenticatorCore - extension SEEncryptedData { +extension SEBaseEncryptedAuthorizationData { var decryptedAuthorizationData: SEAuthorizationData? { if let decryptedDictionary = self.decryptedDictionary { return SEAuthorizationData(decryptedDictionary) @@ -31,11 +33,33 @@ import SEAuthenticator return nil } + var decryptedAuthorizationDataV2: SEAuthorizationDataV2? { + guard let v2Response = self as? SEEncryptedAuthorizationData, + let connectionId = connectionId else { return nil } + + if v2Response.status.isFinal { + return SEAuthorizationDataV2( + id: v2Response.id, + connectionId: connectionId, + status: v2Response.status + ) + } else { + if let decryptedDictionary = self.decryptedDictionary { + return SEAuthorizationDataV2( + decryptedDictionary, + id: v2Response.id, + connectionId: connectionId, + status: v2Response.status + ) + } + } + return nil + } + var decryptedConsentData: SEConsentData? { if let connectionId = self.connectionId, let decryptedDictionary = self.decryptedDictionary { - - return SEConsentData(decryptedDictionary, connectionId) + return SEConsentData(decryptedDictionary, entityId, connectionId) } return nil } diff --git a/Example/Authenticator/Utils/Extensions/StackViewExtensions.swift b/Example/Authenticator/Utils/Extensions/StackViewExtensions.swift index 5835794f..a1fd82be 100644 --- a/Example/Authenticator/Utils/Extensions/StackViewExtensions.swift +++ b/Example/Authenticator/Utils/Extensions/StackViewExtensions.swift @@ -39,4 +39,12 @@ extension UIStackView { func addArrangedSubviews(_ subviews: UIView...) { subviews.forEach { addArrangedSubview($0) } } + + func removeAllArrangedSubviews() { + arrangedSubviews.forEach { + removeArrangedSubview($0) + NSLayoutConstraint.deactivate($0.constraints) + $0.removeFromSuperview() + } + } } diff --git a/Example/Authenticator/Utils/Extensions/StringExtensions.swift b/Example/Authenticator/Utils/Extensions/StringExtensions.swift index 76154419..37c78036 100644 --- a/Example/Authenticator/Utils/Extensions/StringExtensions.swift +++ b/Example/Authenticator/Utils/Extensions/StringExtensions.swift @@ -43,6 +43,14 @@ extension String { return attributedString } + + func capitalizingFirstLetter() -> String { + return prefix(1).uppercased() + self.lowercased().dropFirst() + } + + mutating func capitalizeFirstLetter() { + self = self.capitalizingFirstLetter() + } } private extension NSAttributedString { diff --git a/Example/Authenticator/Utils/Handlers/ConnectHandler.swift b/Example/Authenticator/Utils/Handlers/ConnectHandler.swift index 7426771b..c5d0be7a 100644 --- a/Example/Authenticator/Utils/Handlers/ConnectHandler.swift +++ b/Example/Authenticator/Utils/Handlers/ConnectHandler.swift @@ -21,7 +21,7 @@ // import Foundation -import SEAuthenticator +import SEAuthenticatorCore protocol ConnectEventsDelegate: class { func showWebViewController() @@ -71,7 +71,7 @@ final class ConnectHandler { finalString.append(description) delegate?.finishConnectWithSuccess(attributedMessage: finalString) - if connection.geolocationRequired.value != nil && LocationManager.shared.notDeterminedAuthorization { + if connection.geolocationRequired.value != nil { delegate?.requestLocationAuthorization() } } @@ -82,11 +82,21 @@ final class ConnectHandler { let connectQuery = SEConnectHelper.connectQuery(from: url) delegate?.showWebViewController() - createNewConnection(from: configurationUrl, with: connectQuery) + + let apiVersion = configurationUrl.absoluteString.apiVerion + createNewConnection( + from: configurationUrl, + with: connectQuery, + interactor: apiVersion == "2" ? ConnectionsInteractorV2() : ConnectionsInteractor() + ) } - private func createNewConnection(from configurationUrl: URL, with connectQuery: String?) { - ConnectionsInteractor.createNewConnection( + private func createNewConnection( + from configurationUrl: URL, + with connectQuery: String?, + interactor: BaseConnectionsInteractor + ) { + interactor.createNewConnection( from: configurationUrl, with: connectQuery, success: { [weak self] connection, accessToken in @@ -106,7 +116,10 @@ final class ConnectHandler { private func reconnectConnection(_ connectionId: String) { guard let connection = ConnectionsCollector.with(id: connectionId) else { return } - ConnectionsInteractor.submitNewConnection( + let interactor: BaseConnectionsInteractor = connection.isApiV2 + ? ConnectionsInteractorV2() : ConnectionsInteractor() + + interactor.submitNewConnection( for: connection, connectQuery: nil, success: { [weak self] connection, accessToken in diff --git a/Example/Authenticator/Utils/Handlers/InstantActionHandler.swift b/Example/Authenticator/Utils/Handlers/InstantActionHandler.swift index bc2f16c3..5b679c22 100644 --- a/Example/Authenticator/Utils/Handlers/InstantActionHandler.swift +++ b/Example/Authenticator/Utils/Handlers/InstantActionHandler.swift @@ -22,6 +22,8 @@ import UIKit import SEAuthenticator +import SEAuthenticatorV2 +import SEAuthenticatorCore protocol InstantActionEventsDelegate: class { func shouldPresentConnectionPicker(connections: [Connection]) @@ -33,68 +35,116 @@ protocol InstantActionEventsDelegate: class { final class InstantActionHandler { private var qrUrl: URL - private var actionGuid: GUID - private var connectUrl: URL weak var delegate: InstantActionEventsDelegate? - init(qrUrl: URL, actionGuid: GUID, connectUrl: URL) { + init(qrUrl: URL) { self.qrUrl = qrUrl - self.actionGuid = actionGuid - self.connectUrl = connectUrl + } + + private var actionGuid: GUID? { + return SEConnectHelper.actionGuid(from: qrUrl) + } + + private var connectUrl: URL? { + return SEConnectHelper.connectUrl(from: qrUrl) + } + + private var apiVersion: ApiVersion { + return SEConnectHelper.apiVersion(from: qrUrl) ?? "1" } func startHandling() { - handleQr(qrUrl: qrUrl, actionGuid: actionGuid, connectUrl: connectUrl) + handleQr(qrUrl: qrUrl) } - private func handleQr(qrUrl: URL, actionGuid: GUID, connectUrl: URL) { + func submitAction(for submitData: SubmitActionData) { + if apiVersion == "2" { + guard let providerId = qrUrl.queryItem(for: SENetKeys.providerId), + let actionId = qrUrl.queryItem(for: SENetKeys.actionId) else { return } + + let actionData = SEActionRequestDataV2( + url: submitData.baseUrl, + connectionGuid: submitData.connectionGuid, + accessToken: submitData.accessToken, + appLanguage: UserDefaultsHelper.applicationLanguage, + providerId: providerId, + actionId: actionId, + connectionId: submitData.connectionId + ) + + SEActionManagerV2.submitAction( + data: actionData, + onSuccess: { [weak self] response in + guard let strongSelf = self else { return } + + strongSelf.handleActionResponse(response, qrUrl: strongSelf.qrUrl) + }, + onFailure: { [weak self] _ in + DispatchQueue.main.async { + self?.delegate?.errorReceived(error: l10n(.actionError)) + } + } + ) + } else { + let actionData = SEActionRequestData( + url: connectUrl!, + connectionGuid: submitData.connectionGuid, + accessToken: submitData.accessToken, + appLanguage: UserDefaultsHelper.applicationLanguage, + guid: actionGuid! + ) + + SEActionManager.submitAction( + data: actionData, + onSuccess: { [weak self] response in + guard let strongSelf = self else { return } + + strongSelf.handleActionResponse(response, qrUrl: strongSelf.qrUrl) + }, + onFailure: { [weak self] _ in + DispatchQueue.main.async { + self?.delegate?.errorReceived(error: l10n(.actionError)) + } + } + ) + } + } + + private func handleQr(qrUrl: URL) { guard ConnectionsCollector.activeConnections.count > 0 else { self.delegate?.errorReceived(error: l10n(.noActiveConnection)) return } - let connections = ConnectionsCollector.activeConnections(by: connectUrl) + var connections = [Connection]() + + if apiVersion == "2", let providerId = SEConnectHelper.providerId(from: qrUrl) { + connections = ConnectionsCollector.activeConnections(by: providerId) + } else if let connectUrl = connectUrl { + connections = ConnectionsCollector.activeConnections(by: connectUrl) + } if connections.count > 1 { delegate?.shouldPresentConnectionPicker(connections: connections) } else if connections.count == 1 { - guard let connection = connections.first else { return } + guard let connection = connections.first, let baseUrl = connection.baseUrl else { return } submitAction( - for: connection.guid, - accessToken: connection.accessToken + for: SubmitActionData( + baseUrl: baseUrl, + connectionGuid: connection.guid, + connectionId: connection.id, + accessToken: connection.accessToken, + apiVersion: connection.apiVersion + ) ) } else { delegate?.shouldDismiss(with: l10n(.noSuitableConnection)) } } - func submitAction(for connectionGuid: GUID, accessToken: AccessToken) { - let actionData = SEActionRequestData( - url: connectUrl, - connectionGuid: connectionGuid, - accessToken: accessToken, - appLanguage: UserDefaultsHelper.applicationLanguage, - guid: actionGuid - ) - - SEActionManager.submitAction( - data: actionData, - onSuccess: { [weak self] response in - guard let strongSelf = self else { return } - - strongSelf.handleActionResponse(response, qrUrl: strongSelf.qrUrl) - }, - onFailure: { [weak self] _ in - DispatchQueue.main.async { - self?.delegate?.errorReceived(error: l10n(.actionError)) - } - } - ) - } - - private func handleActionResponse(_ response: SESubmitActionResponse, qrUrl: URL) { + private func handleActionResponse(_ response: SEBaseActionResponse, qrUrl: URL) { if let connectionId = response.connectionId, let authorizationId = response.authorizationId { delegate?.showAuthorization(connectionId: connectionId, authorizationId: authorizationId) diff --git a/Example/Authenticator/Utils/Helpers/ReachabilityManager.swift b/Example/Authenticator/Utils/Helpers/ConnectivityManager.swift similarity index 93% rename from Example/Authenticator/Utils/Helpers/ReachabilityManager.swift rename to Example/Authenticator/Utils/Helpers/ConnectivityManager.swift index 26467e43..846b21a5 100644 --- a/Example/Authenticator/Utils/Helpers/ReachabilityManager.swift +++ b/Example/Authenticator/Utils/Helpers/ConnectivityManager.swift @@ -23,12 +23,16 @@ import Foundation import Reachability -class ReachabilityManager { - static let shared = ReachabilityManager() +protocol Connectable { + var isConnected: Bool { get } +} + +class ConnectivityManager: Connectable { + static let shared = ConnectivityManager() private var reachability: Reachability! - var isReachable: Bool { + var isConnected: Bool { return reachability.connection != .unavailable } diff --git a/Example/Authenticator/Utils/Helpers/Constants.swift b/Example/Authenticator/Utils/Helpers/Constants.swift index c8a9c1b6..53a44339 100644 --- a/Example/Authenticator/Utils/Helpers/Constants.swift +++ b/Example/Authenticator/Utils/Helpers/Constants.swift @@ -32,6 +32,8 @@ func after(_ time: Double, _ doBlock: @escaping () -> ()) { } let finalAuthorizationTimeToLive: Double = 4.0 +let API_V1_VERSION = "1" +let API_V2_VERSION = "2" struct AnimationConstants { static let defaultDuration: CGFloat = 0.4 diff --git a/Example/Authenticator/Utils/Helpers/Localizations.swift b/Example/Authenticator/Utils/Helpers/Localizations.swift index e6df4110..0301a413 100644 --- a/Example/Authenticator/Utils/Helpers/Localizations.swift +++ b/Example/Authenticator/Utils/Helpers/Localizations.swift @@ -43,6 +43,7 @@ enum Localizations: String, Localizable { case forgot = "actions.forgot" case remove = "actions.remove" case revoke = "actions.revoke" + case accessToLocation = "actions.access_to_location" // MARK: - Onboarding case getStarted = "actions.get_started" @@ -104,6 +105,8 @@ enum Localizations: String, Localizable { case forgotPasscodeClearDataDescription = "no_data.forgot_passcode_clear_data_description" case locationWarning = "location.warning" + case ipAddress = "in_app.authorizations.ip_address" + // MARK: - Actions case newAction = "instant_action.new_action" case instantActionSuccessMessage = "instant_action.success_message" @@ -199,11 +202,18 @@ enum Localizations: String, Localizable { case noInternetConnection = "errors.no_internet_connection" case errorOccuredPleaseTryAgain = "errors.authorization_error" case pleaseTryAgain = "errors.no_internet_connection_try_again" + case pleaseCheckAndTryAgain = "errors.no_internet_connection_check_and_try_again" case inactivityMessage = "warnings.inactivity_block_message" case passcodeDontMatch = "errors.passcode_dont_match" case wrongPasscode = "errors.wrong_passcode" case biometricsNotAvailable = "in_app.settings.biometrics_not_available" case biometricsNotAvailableDescription = "in_app.settings.biometrics_not_available_message" + case turnOnLocationServices = "errors.turn_on_location_services" + case turnOnLocationSharingDescription = "errors.turn_on_location_sharing_description" + case turnOnPhoneLocationServicesDescription = "errors.turn_on_phone_location_services" + + case accessToLocationServices = "errors.access_to_location_services" + case grantAccessToLocationServices = "warnings.grant_access_to_location_services" // MARK: - Connection Options case connect = "actions.connect" @@ -212,6 +222,7 @@ enum Localizations: String, Localizable { case contactSupport = "in_app.settings.contact_support" case reportAnIssue = "actions.report_an_issue" case viewConsents = "actions.view_consents" + case id = "actions.view_id" // MARK: - Main Menu Options case viewConnections = "actions.view_connections" diff --git a/Example/Authenticator/Utils/Helpers/LocationManager.swift b/Example/Authenticator/Utils/Helpers/LocationManager.swift index 81ae262a..152704f4 100644 --- a/Example/Authenticator/Utils/Helpers/LocationManager.swift +++ b/Example/Authenticator/Utils/Helpers/LocationManager.swift @@ -26,7 +26,7 @@ import CoreLocation protocol LocationManagement { var notDeterminedAuthorization: Bool { get } var geolocationSharingIsEnabled: Bool { get } - func showLocationWarning(connection: Connection?) -> Bool + func shouldShowLocationWarning(connection: Connection?) -> Bool } final class LocationManager: NSObject, LocationManagement, CLLocationManagerDelegate { @@ -49,7 +49,11 @@ final class LocationManager: NSObject, LocationManagement, CLLocationManagerDele .contains(CLLocationManager.authorizationStatus()) } - func showLocationWarning(connection: Connection?) -> Bool { + var geolocationSharingIsDenied: Bool { + return CLLocationManager.authorizationStatus() == .denied + } + + func shouldShowLocationWarning(connection: Connection?) -> Bool { return (connection?.geolocationRequired.value ?? false) && !geolocationSharingIsEnabled } @@ -73,6 +77,7 @@ final class LocationManager: NSObject, LocationManagement, CLLocationManagerDele } func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { + NotificationsHelper.post(.locationServicesStatusDidChange) if status == .authorizedAlways || status == .authorizedWhenInUse { startUpdatingLocation() } diff --git a/Example/Authenticator/Utils/Helpers/NotificationsHelper.swift b/Example/Authenticator/Utils/Helpers/NotificationsHelper.swift index 741874e6..5b6b5ca5 100644 --- a/Example/Authenticator/Utils/Helpers/NotificationsHelper.swift +++ b/Example/Authenticator/Utils/Helpers/NotificationsHelper.swift @@ -25,6 +25,7 @@ import Foundation extension Notification.Name { static let networkConnectionIsNotReachable = Notification.Name("network-connection-is-not-reachable") static let networkConnectionIsReachable = Notification.Name("network-connection-is-reachable") + static let locationServicesStatusDidChange = Notification.Name("locationServicesStatusDidChange") } final class NotificationsHelper { diff --git a/Example/Authenticator/Utils/Interactors/AuthorizationsInteractor.swift b/Example/Authenticator/Utils/Interactors/AuthorizationsInteractor.swift deleted file mode 100644 index 32d6590d..00000000 --- a/Example/Authenticator/Utils/Interactors/AuthorizationsInteractor.swift +++ /dev/null @@ -1,90 +0,0 @@ -// -// AuthorizationsInteractor.swift -// This file is part of the Salt Edge Authenticator distribution -// (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 3 or later. -// -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -// For the additional permissions granted for Salt Edge Authenticator -// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md -// - -import Foundation -import SEAuthenticator - -struct AuthorizationsInteractor { - static func confirm( - data: SEConfirmAuthorizationRequestData, - success: (() -> ())? = nil, - failure: ((String) -> ())? = nil - ) { - SEAuthorizationManager.confirmAuthorization( - data: data, - onSuccess: { _ in - success?() - }, - onFailure: { error in - failure?(error) - } - ) - } - - static func deny( - data: SEConfirmAuthorizationRequestData, - success: (() -> ())? = nil, - failure: ((String) -> ())? = nil - ) { - SEAuthorizationManager.denyAuthorization( - data: data, - onSuccess: { _ in - success?() - }, - onFailure: { error in - failure?(error) - } - ) - } - - static func refresh( - connection: Connection, - authorizationId: ID, - success: @escaping (SEEncryptedData) -> (), - failure: ((String) -> ())? = nil, - connectionNotFoundFailure: @escaping ((String?) -> ()) - ) { - let accessToken = connection.accessToken - - guard let baseUrl = connection.baseUrl else { failure?(l10n(.somethingWentWrong)); return } - - SEAuthorizationManager.getEncryptedAuthorization( - data: SEBaseAuthenticatedWithIdRequestData( - url: baseUrl, - connectionGuid: connection.guid, - accessToken: accessToken, - appLanguage: UserDefaultsHelper.applicationLanguage, - entityId: authorizationId - ), - onSuccess: { response in - success(response.data) - }, - onFailure: { error in - if SEAPIError.connectionNotFound.isConnectionNotFound(error) { - connectionNotFoundFailure(connection.id) - } else { - failure?("\(l10n(.somethingWentWrong)) (\(connection.name))") - } - } - ) - } -} diff --git a/Example/Authenticator/Utils/Interactors/Base/AuthorizationsInteractor.swift b/Example/Authenticator/Utils/Interactors/Base/AuthorizationsInteractor.swift new file mode 100644 index 00000000..946df450 --- /dev/null +++ b/Example/Authenticator/Utils/Interactors/Base/AuthorizationsInteractor.swift @@ -0,0 +1,126 @@ +// +// AuthorizationsInteractor.swift +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2019 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticator +import SEAuthenticatorV2 +import SEAuthenticatorCore + +struct AuthorizationsInteractor { + static func confirm( + apiVersion: ApiVersion, + data: SEConfirmAuthorizationRequestData, + successV1: (() -> ())? = nil, + successV2: ((SEConfirmAuthorizationResponseV2) -> ())? = nil, + failure: ((String) -> ())? = nil + ) { + if apiVersion == "2" { + SEAuthorizationManagerV2.confirmAuthorization( + data: data, + onSuccess: { response in successV2?(response) }, + onFailure: { error in failure?(error) } + ) + } else { + SEAuthorizationManager.confirmAuthorization( + data: data, + onSuccess: { _ in successV1?() }, + onFailure: { error in failure?(error) } + ) + } + } + + static func deny( + apiVersion: ApiVersion, + data: SEConfirmAuthorizationRequestData, + successV1: (() -> ())? = nil, + successV2: ((SEConfirmAuthorizationResponseV2) -> ())? = nil, + failure: ((String) -> ())? = nil + ) { + if apiVersion == "2" { + SEAuthorizationManagerV2.denyAuthorization( + data: data, + onSuccess: { response in successV2?(response) }, + onFailure: { error in failure?(error) } + ) + } else { + SEAuthorizationManager.denyAuthorization( + data: data, + onSuccess: { _ in successV1?() }, + onFailure: { error in failure?(error) } + ) + } + } + + static func refresh( + connection: Connection, + authorizationId: ID, + success: @escaping (SEBaseEncryptedAuthorizationData) -> (), + failure: ((String) -> ())? = nil, + connectionNotFoundFailure: @escaping ((String?) -> ()) + ) { + let accessToken = connection.accessToken + + guard let baseUrl = connection.baseUrl else { failure?(l10n(.somethingWentWrong)); return } + + if connection.isApiV2 { + SEAuthorizationManagerV2.getEncryptedAuthorization( + data: SEBaseAuthenticatedWithIdRequestData( + url: baseUrl, + connectionGuid: connection.guid, + accessToken: accessToken, + appLanguage: UserDefaultsHelper.applicationLanguage, + entityId: authorizationId + ), + onSuccess: { response in + success(response.data) + }, + onFailure: { error in + if SEAPIError.connectionNotFound.isConnectionNotFound(error) { + connectionNotFoundFailure(connection.id) + } else { + failure?("\(l10n(.somethingWentWrong)) (\(connection.name))") + } + } + ) + } else { + SEAuthorizationManager.getEncryptedAuthorization( + data: SEBaseAuthenticatedWithIdRequestData( + url: baseUrl, + connectionGuid: connection.guid, + accessToken: accessToken, + appLanguage: UserDefaultsHelper.applicationLanguage, + entityId: authorizationId + ), + onSuccess: { response in + success(response.data) + }, + onFailure: { error in + if SEAPIError.connectionNotFound.isConnectionNotFound(error) { + connectionNotFoundFailure(connection.id) + } else { + failure?("\(l10n(.somethingWentWrong)) (\(connection.name))") + } + } + ) + } + } +} diff --git a/Example/Authenticator/Utils/Interactors/Base/BaseConnectionsInteractor.swift b/Example/Authenticator/Utils/Interactors/Base/BaseConnectionsInteractor.swift new file mode 100644 index 00000000..d0198028 --- /dev/null +++ b/Example/Authenticator/Utils/Interactors/Base/BaseConnectionsInteractor.swift @@ -0,0 +1,42 @@ +// +// BaseConnectionsInteractor +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +protocol BaseConnectionsInteractor { + func createNewConnection( + from url: URL, + with connectQuery: String?, + success: @escaping (Connection, AccessToken) -> (), + redirect: @escaping (Connection, String) -> (), + failure: @escaping (String) -> () + ) + func submitNewConnection( + for connection: Connection, + connectQuery: String?, + success: @escaping (Connection, AccessToken) -> (), + redirect: @escaping (Connection, String) -> (), + failure: @escaping (String) -> () + ) + func revoke(_ connection: Connection, success: (() -> ())?, failure: @escaping (String) -> ()) +} diff --git a/Example/Authenticator/Utils/Interactors/CollectionsInteractor.swift b/Example/Authenticator/Utils/Interactors/Base/CollectionsInteractor.swift similarity index 57% rename from Example/Authenticator/Utils/Interactors/CollectionsInteractor.swift rename to Example/Authenticator/Utils/Interactors/Base/CollectionsInteractor.swift index bff71c4e..47924ac0 100644 --- a/Example/Authenticator/Utils/Interactors/CollectionsInteractor.swift +++ b/Example/Authenticator/Utils/Interactors/Base/CollectionsInteractor.swift @@ -22,14 +22,17 @@ import Foundation import SEAuthenticator +import SEAuthenticatorV2 +import SEAuthenticatorCore +// TODO: Remade to on interator enum CollectionsInteractor { case authorizations case consents func refresh( connection: Connection, - success: @escaping ([SEEncryptedData]) -> (), + success: @escaping ([SEBaseEncryptedAuthorizationData]) -> (), failure: ((String) -> ())? = nil, connectionNotFoundFailure: @escaping ((String?) -> ()) ) { @@ -52,27 +55,43 @@ enum CollectionsInteractor { switch self { case .authorizations: - SEAuthorizationManager.getEncryptedAuthorizations( - data: requestData, - onSuccess: { response in success(response.data) }, - onFailure: { error in onFailure(error: error, connection: connection) } - ) + if connection.isApiV2 { + SEAuthorizationManagerV2.getEncryptedAuthorizations( + data: requestData, + onSuccess: { response in success(response.data) }, + onFailure: { error in onFailure(error: error, connection: connection) } + ) + } else { + SEAuthorizationManager.getEncryptedAuthorizations( + data: requestData, + onSuccess: { response in success(response.data) }, + onFailure: { error in onFailure(error: error, connection: connection) } + ) + } case .consents: - SEConsentsManager.getEncryptedConsents( - data: requestData, - onSuccess: { response in success(response.data) }, - onFailure: { error in onFailure(error: error, connection: connection) } - ) + if connection.isApiV2 { + SEConsentManagerV2.getEncryptedConsents( + data: requestData, + onSuccess: { response in success(response.data) }, + onFailure: { error in onFailure(error: error, connection: connection) } + ) + } else { + SEConsentManager.getEncryptedConsents( + data: requestData, + onSuccess: { response in success(response.data) }, + onFailure: { error in onFailure(error: error, connection: connection) } + ) + } } } func refresh( connections: [Connection], - success: @escaping ([SEEncryptedData]) -> (), + success: @escaping ([SEBaseEncryptedAuthorizationData]) -> (), failure: ((String) -> ())? = nil, connectionNotFoundFailure: @escaping ((String?) -> ()) ) { - var encryptedAuthorizations = [SEEncryptedData]() + var encryptedAuthorizations = [SEBaseEncryptedAuthorizationData]() var numberOfResponses = 0 @@ -84,7 +103,7 @@ enum CollectionsInteractor { } } - func onSuccess(data: [SEEncryptedData]) { + func onSuccess(data: [SEBaseEncryptedAuthorizationData]) { encryptedAuthorizations.append(contentsOf: data) incrementAndCheckResponseCount() @@ -112,17 +131,40 @@ enum CollectionsInteractor { switch self { case .authorizations: - SEAuthorizationManager.getEncryptedAuthorizations( - data: requestData, - onSuccess: { response in onSuccess(data: response.data) }, - onFailure: { error in onFailure(error: error, connection: connection) } - ) + if connection.isApiV2 { + SEAuthorizationManagerV2.getEncryptedAuthorizations( + data: requestData, + onSuccess: { response in onSuccess(data: response.data) }, + onFailure: { error in onFailure(error: error, connection: connection) } + ) + } else { + SEAuthorizationManager.getEncryptedAuthorizations( + data: requestData, + onSuccess: { response in onSuccess(data: response.data) }, + onFailure: { error in onFailure(error: error, connection: connection) } + ) + } case .consents: - SEConsentsManager.getEncryptedConsents( - data: requestData, - onSuccess: { response in onSuccess(data: response.data) }, - onFailure: { error in onFailure(error: error, connection: connection) } - ) + if connection.isApiV2 { + SEConsentManagerV2.getEncryptedConsents( + data: requestData, + onSuccess: { response in + onSuccess(data: response.data) + }, onFailure: { error in + onFailure(error: error, connection: connection) + } + ) + } else { + SEConsentManager.getEncryptedConsents( + data: requestData, + onSuccess: { response in + onSuccess(data: response.data) + }, + onFailure: { error in + onFailure(error: error, connection: connection) + } + ) + } } } } diff --git a/Example/Authenticator/Utils/Interactors/ConsentsInteractor.swift b/Example/Authenticator/Utils/Interactors/Base/ConsentsInteractor.swift similarity index 68% rename from Example/Authenticator/Utils/Interactors/ConsentsInteractor.swift rename to Example/Authenticator/Utils/Interactors/Base/ConsentsInteractor.swift index a49cbe11..740439e6 100644 --- a/Example/Authenticator/Utils/Interactors/ConsentsInteractor.swift +++ b/Example/Authenticator/Utils/Interactors/Base/ConsentsInteractor.swift @@ -22,28 +22,35 @@ import Foundation import SEAuthenticator +import SEAuthenticatorV2 +import SEAuthenticatorCore struct ConsentsInteractor { static func revoke(_ consent: SEConsentData, success: (() -> ())? = nil, failure: ((String) -> ())? = nil) { guard let connection = ConnectionsCollector.with(id: consent.connectionId), - let baseUrl = connection.baseUrl else { failure?(l10n(.somethingWentWrong)); return } + let baseUrl = connection.baseUrl, + let consentId = consent.id else { failure?(l10n(.somethingWentWrong)); return } let data = SEBaseAuthenticatedWithIdRequestData( url: baseUrl, connectionGuid: connection.guid, accessToken: connection.accessToken, appLanguage: UserDefaultsHelper.applicationLanguage, - entityId: consent.id + entityId: consentId ) - SEConsentsManager.revokeConsent( - data: data, - onSuccess: { _ in - success?() - }, - onFailure: { error in - failure?(error) - } - ) + if connection.isApiV2 { + SEConsentManagerV2.revokeConsent( + data: data, + onSuccess: { _ in success?() }, + onFailure: { error in failure?(error) } + ) + } else { + SEConsentManager.revokeConsent( + data: data, + onSuccess: { _ in success?() }, + onFailure: { error in failure?(error) } + ) + } } } diff --git a/Example/Authenticator/Utils/Interactors/ConnectionsInteractor.swift b/Example/Authenticator/Utils/Interactors/v1/ConnectionsInteractor.swift similarity index 89% rename from Example/Authenticator/Utils/Interactors/ConnectionsInteractor.swift rename to Example/Authenticator/Utils/Interactors/v1/ConnectionsInteractor.swift index 3f7f3d20..3b6daea8 100644 --- a/Example/Authenticator/Utils/Interactors/ConnectionsInteractor.swift +++ b/Example/Authenticator/Utils/Interactors/v1/ConnectionsInteractor.swift @@ -22,9 +22,10 @@ import Foundation import SEAuthenticator +import SEAuthenticatorCore -struct ConnectionsInteractor { - static func createNewConnection( +struct ConnectionsInteractor: BaseConnectionsInteractor { + func createNewConnection( from url: URL, with connectQuery: String?, success: @escaping (Connection, AccessToken) -> (), @@ -45,7 +46,7 @@ struct ConnectionsInteractor { connection.supportEmail = response.supportEmail connection.logoUrlString = response.logoUrl?.absoluteString ?? "" - connection.baseUrlString = response.connectUrl.absoluteString + connection.baseUrlString = response.baseUrl.absoluteString connection.geolocationRequired.value = response.geolocationRequired submitNewConnection( @@ -60,7 +61,7 @@ struct ConnectionsInteractor { ) } - static func fetchProviderConfiguration( + func fetchProviderConfiguration( from url: URL, success: @escaping (SEProviderResponse) -> (), failure: @escaping (String) -> () @@ -72,7 +73,7 @@ struct ConnectionsInteractor { ) } - static func submitNewConnection( + func submitNewConnection( for connection: Connection, connectQuery: String?, success: @escaping (Connection, AccessToken) -> (), @@ -80,7 +81,7 @@ struct ConnectionsInteractor { failure: @escaping (String) -> () ) { guard let connectionData = SECreateConnectionRequestData(code: connection.code, tag: connection.guid), - let connectUrl = connection.baseUrl?.appendingPathComponent(SENetPaths.connections.path) else { return } + let connectUrl = connection.baseUrl else { return } SEConnectionManager.createConnection( by: connectUrl, @@ -102,9 +103,10 @@ struct ConnectionsInteractor { ) } - static func revoke( + func revoke( _ connection: Connection, - success: (() -> ())? = nil + success: (() -> ())?, + failure: @escaping (String) -> () ) { guard let baseUrl = connection.baseUrl else { return } @@ -121,9 +123,7 @@ struct ConnectionsInteractor { onSuccess: { _ in success?() }, - onFailure: { error in - Log.debugLog(message: error) - } + onFailure: failure ) } } diff --git a/Example/Authenticator/Utils/Interactors/v2/ConnectionsInteractorV2.swift b/Example/Authenticator/Utils/Interactors/v2/ConnectionsInteractorV2.swift new file mode 100644 index 00000000..f9d17a57 --- /dev/null +++ b/Example/Authenticator/Utils/Interactors/v2/ConnectionsInteractorV2.swift @@ -0,0 +1,157 @@ +// +// ConnectionsInteractorV2 +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorV2 +import SEAuthenticatorCore + +struct ConnectionsInteractorV2: BaseConnectionsInteractor { + /* + Request to create new SCA Service connection. + Result is returned through callback. + */ + func createNewConnection( + from url: URL, + with connectQuery: String?, + success: @escaping (Connection, AccessToken) -> (), + redirect: @escaping (Connection, String) -> (), + failure: @escaping (String) -> () + ) { + getProviderConfiguration( + from: url, + success: { response in + let connection = Connection() + + if ConnectionsCollector.connectionNames.contains(response.name) { + connection.name = "\(response.name) (\(ConnectionsCollector.connectionNames.count + 1))" + } else { + connection.name = response.name + } + + connection.providerId = response.providerId + connection.publicKey = response.publicKey + connection.apiVersion = response.apiVersion + connection.supportEmail = response.supportEmail + connection.logoUrlString = response.logoUrl?.absoluteString ?? "" + connection.baseUrlString = response.baseUrl.absoluteString + connection.geolocationRequired.value = response.geolocationRequired + + submitNewConnection( + for: connection, + connectQuery: connectQuery, + success: success, + redirect: redirect, + failure: failure + ) + }, + failure: failure + ) + } + + /* + Request to get SCA Service connection. + Result is returned through callback. + */ + func getProviderConfiguration( + from url: URL, + success: @escaping (SEProviderResponseV2) -> (), + failure: @escaping (String) -> () + ) { + SEProviderManagerV2.fetchProviderData( + url: url, + onSuccess: success, + onFailure: failure + ) + } + + func submitNewConnection( + for connection: Connection, + connectQuery: String?, + success: @escaping (Connection, AccessToken) -> (), + redirect: @escaping (Connection, String) -> (), + failure: @escaping (String) -> () + ) { + // 1. Create Provider's public key (SecKey) + SECryptoHelper.createKey( + from: connection.publicKey, + isPublic: true, + tag: connection.providerPublicKeyTag + ) + + // 2. Generate new RSA key pair for a new Connection by tag + SECryptoHelper.createKeyPair(with: SETagHelper.create(for: connection.guid)) + + // 3. Convert Connection's public key to pem + guard let connectionPublicKeyPem = SECryptoHelper.publicKeyToPem(tag: SETagHelper.create(for: connection.guid)), + // 4. Encrypt Connection's public key with Provider's public key (step 1) + let encryptedData = try? SECryptoHelper.encrypt( + connectionPublicKeyPem, + tag: connection.providerPublicKeyTag + ), + let providerId = connection.providerId else { return } + + let params = SECreateConnectionParams( + providerId: providerId, + pushToken: UserDefaultsHelper.pushToken, + connectQuery: connectQuery, + encryptedRsaPublicKey: encryptedData + ) + + guard let connectUrl = connection.baseUrl else { return } + + // 5. Send request + SEConnectionManagerV2.createConnection( + by: connectUrl, + params: params, + appLanguage: UserDefaultsHelper.applicationLanguage, + onSuccess: { response in + connection.id = "\(response.id)" + redirect(connection, response.authenticationUrl) + }, + onFailure: failure + ) + } + + func revoke( + _ connection: Connection, + success: (() -> ())?, + failure: @escaping (String) -> () + ) { + guard let baseUrl = connection.baseUrl else { return } + + let data = SEBaseAuthenticatedWithIdRequestData( + url: baseUrl, + connectionGuid: connection.guid, + accessToken: connection.accessToken, + appLanguage: UserDefaultsHelper.applicationLanguage, + entityId: connection.id + ) + + SEConnectionManagerV2.revokeConnection( + data: data, + onSuccess: { _ in + success?() + }, + onFailure: failure + ) + } +} diff --git a/Example/Authenticator/View Controllers/AuthorizationsViewController.swift b/Example/Authenticator/View Controllers/AuthorizationsViewController.swift index ad41deeb..ebd01830 100644 --- a/Example/Authenticator/View Controllers/AuthorizationsViewController.swift +++ b/Example/Authenticator/View Controllers/AuthorizationsViewController.swift @@ -25,6 +25,7 @@ import UIKit protocol AuthorizationsViewControllerDelegate: class { func scanQrPressed() func showMoreOptionsMenu() + func requestLocation() } private struct Layout { @@ -98,21 +99,44 @@ final class AuthorizationsViewController: BaseViewController { message: l10n(.deniedCameraDescription), confirmActionTitle: l10n(.goToSettings), confirmActionStyle: .default, - confirmAction: { _ in - guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else { return } - - if UIApplication.shared.canOpenURL(settingsUrl) { - UIApplication.shared.open(settingsUrl) - } - } + confirmAction: { _ in strongSelf.openPhoneSettings() } ) } + case .requestLocationWarning: + strongSelf.checkLocationServicesStatus() default: break } strongSelf.viewModel.resetState() } } + private func checkLocationServicesStatus() { + if LocationManager.shared.notDeterminedAuthorization { + LocationManager.shared.requestLocationAuthorization() + } else if LocationManager.shared.geolocationSharingIsDenied { + showConfirmationAlert( + withTitle: l10n(.accessToLocationServices), + message: l10n(.turnOnLocationSharingDescription), + confirmActionTitle: l10n(.goToSettings), + confirmActionStyle: .default, + confirmAction: { _ in self.openPhoneSettings() } + ) + } else { + showInfoAlert( + withTitle: l10n(.turnOnLocationServices), + message: l10n(.turnOnPhoneLocationServicesDescription) + ) + } + } + + private func openPhoneSettings() { + guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else { return } + + if UIApplication.shared.canOpenURL(settingsUrl) { + UIApplication.shared.open(settingsUrl) + } + } + private func setupNoDataView() { noDataView = NoDataView(data: viewModel.emptyViewData, action: scanQrPressed) } diff --git a/Example/Authenticator/View Controllers/Connection/ConnectViewController.swift b/Example/Authenticator/View Controllers/Connection/ConnectViewController.swift index 00602668..777c2404 100644 --- a/Example/Authenticator/View Controllers/Connection/ConnectViewController.swift +++ b/Example/Authenticator/View Controllers/Connection/ConnectViewController.swift @@ -24,28 +24,14 @@ import UIKit final class ConnectViewController: BaseViewController { private lazy var completeView = CompleteView(state: .processing, title: l10n(.processing)) + private let viewModel = ConnectViewModel(reachabilityManager: ConnectivityManager.shared) override func viewDidLoad() { super.viewDidLoad() - checkInternetConnection() + viewModel.checkInternetConnection() setupCancelButton() layout() } - - private func checkInternetConnection() { - guard ReachabilityManager.shared.isReachable else { - self.showInfoAlert( - withTitle: l10n(.noInternetConnection), - message: l10n(.pleaseTryAgain), - actionTitle: l10n(.ok), - completion: { - self.dismiss(animated: true) - } - ) - return - } - } - } // MARK: - Layout @@ -64,9 +50,13 @@ private extension ConnectViewController { title: l10n(.cancel), style: .plain, target: self, - action: #selector(close) + action: #selector(cancelPressed) ) } + + @objc func cancelPressed() { + dismiss(animated: true) + } } // MARK: - Actions @@ -88,6 +78,18 @@ extension ConnectViewController { // MARK: - CompleteViewDelegate extension ConnectViewController: CompleteViewDelegate { func proceedPressed(for view: CompleteView) { - dismiss(animated: true, completion: nil) + cancelPressed() + } +} + +// MARK: - ConnectViewModelEventsDelegate +extension ConnectViewController: ConnectViewModelEventsDelegate { + func showNoInternetConnectionAlert() { + showInfoAlert( + withTitle: l10n(.noInternetConnection), + message: l10n(.pleaseTryAgain), + actionTitle: l10n(.ok), + completion: { self.cancelPressed() } + ) } } diff --git a/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift b/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift index 16921efd..fc7d95d7 100644 --- a/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift +++ b/Example/Authenticator/View Controllers/Connection/ConnectionsViewController.swift @@ -41,6 +41,7 @@ final class ConnectionsViewController: UITableViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) + reloadData() viewModel.refreshConsents() } @@ -53,12 +54,7 @@ final class ConnectionsViewController: UITableViewController { setupRefreshControl() layout() updateViewsHiddenState() - NotificationsHelper.observe( - self, - selector: #selector(reloadData), - name: NSLocale.currentLocaleDidChangeNotification, - object: nil - ) + setupObservers() } @objc private func reloadData() { @@ -86,6 +82,20 @@ final class ConnectionsViewController: UITableViewController { // MARK: - Setup private extension ConnectionsViewController { + func setupObservers() { + NotificationsHelper.observe( + self, + selector: #selector(reloadData), + name: NSLocale.currentLocaleDidChangeNotification, + object: nil + ) + NotificationsHelper.observe( + self, + selector: #selector(reloadData), + name: .locationServicesStatusDidChange, + object: nil + ) + } func setupTableView() { tableView.delegate = self tableView.dataSource = self @@ -190,27 +200,34 @@ extension ConnectionsViewController: ConnectionCellEventsDelegate { viewModel.updateName(by: id) } - func supportPressed(email: String) { - viewModel.showSupport(email: email) - } - - func deletePressed(id: String, showConfirmation: Bool) { - if showConfirmation { + func accessLocationPressed() { + if LocationManager.shared.notDeterminedAuthorization { + LocationManager.shared.requestLocationAuthorization() + } else { showConfirmationAlert( - withTitle: l10n(.deleteConnection), - message: l10n(.deleteConnectionDescription), - confirmActionTitle: l10n(.delete), - confirmActionStyle: .destructive, - cancelTitle: l10n(.cancel), + withTitle: l10n(.accessToLocationServices), + message: l10n(.turnOnLocationSharingDescription), + confirmActionTitle: l10n(.goToSettings), + confirmActionStyle: .default, confirmAction: { _ in - self.viewModel.remove(by: id) + guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else { return } + + if UIApplication.shared.canOpenURL(settingsUrl) { + UIApplication.shared.open(settingsUrl) + } } ) - } else { - viewModel.remove(by: id) } } + func supportPressed(email: String) { + viewModel.showSupport(email: email) + } + + func deletePressed(id: String, showConfirmation: Bool) { + viewModel.checkInternetAndRemoveConnection(id: id, showConfirmation: showConfirmation) + } + func reconnectPreseed(id: String) { viewModel.reconnect(id: id) } diff --git a/Example/Authenticator/View Controllers/ConnectorWebViewController.swift b/Example/Authenticator/View Controllers/ConnectorWebViewController.swift index 855ccfb6..38b61559 100644 --- a/Example/Authenticator/View Controllers/ConnectorWebViewController.swift +++ b/Example/Authenticator/View Controllers/ConnectorWebViewController.swift @@ -22,7 +22,7 @@ import UIKit import WebKit -import SEAuthenticator +import SEAuthenticatorCore protocol ConnectorWebViewControllerDelegate: WKWebViewControllerDelegate { func connectorConfirmed(url: URL, accessToken: AccessToken) diff --git a/Example/Authenticator/View Controllers/QRCodeViewController.swift b/Example/Authenticator/View Controllers/QRCodeViewController.swift index 1d084082..cdd0a3d6 100644 --- a/Example/Authenticator/View Controllers/QRCodeViewController.swift +++ b/Example/Authenticator/View Controllers/QRCodeViewController.swift @@ -142,8 +142,7 @@ final class QRCodeViewController: BaseViewController { } @objc private func cancelPressed() { - dismiss(animated: true) - shouldDismissClosure?() + dismiss(animated: true, completion: shouldDismissClosure) } private func labelsStackView() -> UIStackView { diff --git a/Example/Authenticator/View Controllers/Settings/SettingsViewController.swift b/Example/Authenticator/View Controllers/Settings/SettingsViewController.swift index 4e302802..dff6290f 100644 --- a/Example/Authenticator/View Controllers/Settings/SettingsViewController.swift +++ b/Example/Authenticator/View Controllers/Settings/SettingsViewController.swift @@ -43,14 +43,14 @@ final class SettingsViewController: BaseViewController { layout() } - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - tableView.reloadData() - } +// override func viewWillAppear(_ animated: Bool) { +// super.viewWillAppear(animated) +// tableView.reloadData() +// } - func reloadData() { - tableView.reloadData() - } +// func reloadData() { +// tableView.reloadData() +// } } // MARK: - Setup diff --git a/Example/Authenticator/View Models/Authorizations/AuthorizationDetailViewModel.swift b/Example/Authenticator/View Models/Authorizations/AuthorizationDetailViewModel.swift index 09f7ad0a..91ffd7c7 100644 --- a/Example/Authenticator/View Models/Authorizations/AuthorizationDetailViewModel.swift +++ b/Example/Authenticator/View Models/Authorizations/AuthorizationDetailViewModel.swift @@ -22,10 +22,12 @@ import Foundation import SEAuthenticator +import SEAuthenticatorV2 +import SEAuthenticatorCore protocol AuthorizationDetailEventsDelegate: class { - func confirmPressed(_ authorizationId: String) - func denyPressed(_ authorizationId: String) + func confirmPressed(_ authorizationId: String, apiVersion: ApiVersion) + func denyPressed(_ authorizationId: String, apiVersion: ApiVersion) func authorizationExpired() } @@ -34,6 +36,8 @@ final class AuthorizationDetailViewModel: Equatable { var authorizationId: String var connectionId: String var description: String = "" + var status: AuthorizationStatus? + var descriptionAttributes: [String: Any] = [:] var authorizationCode: String? var lifetime: Int = 0 var authorizationExpiresAt: Date = Date() @@ -43,21 +47,27 @@ final class AuthorizationDetailViewModel: Equatable { authorizationExpiresAt < Date() } var state = Observable(.base) - var showLocationWarning: Bool + var apiVersion: ApiVersion weak var delegate: AuthorizationDetailEventsDelegate? - init?(_ data: SEAuthorizationData, showLocationWarning: Bool) { + init?(_ data: SEBaseAuthorizationData, apiVersion: ApiVersion) { + if let dataV1 = data as? SEAuthorizationData { + self.title = dataV1.title + self.description = dataV1.description + } else if let dataV2 = data as? SEAuthorizationDataV2 { + self.title = dataV2.title + self.descriptionAttributes = dataV2.description + self.status = dataV2.status + } + self.apiVersion = apiVersion self.authorizationId = data.id self.connectionId = data.connectionId self.authorizationCode = data.authorizationCode - self.title = data.title - self.description = data.description self.authorizationExpiresAt = data.expiresAt self.lifetime = Int(data.expiresAt.timeIntervalSince(data.createdAt)) self.createdAt = data.createdAt - self.state.value = data.expiresAt < Date() ? .expired : .base - self.showLocationWarning = showLocationWarning + self.state.value = data.expiresAt < Date() ? .timeOut : .base } static func == (lhs: AuthorizationDetailViewModel, rhs: AuthorizationDetailViewModel) -> Bool { @@ -65,7 +75,8 @@ final class AuthorizationDetailViewModel: Equatable { lhs.connectionId == rhs.connectionId && lhs.title == rhs.title && lhs.description == rhs.description && - lhs.createdAt == rhs.createdAt + lhs.createdAt == rhs.createdAt && + lhs.apiVersion == rhs.apiVersion } var authorizationExpired: Bool = false { @@ -76,11 +87,26 @@ final class AuthorizationDetailViewModel: Equatable { } } + var shouldShowLocationWarning: Bool { + guard let connection = ConnectionsCollector.active(by: connectionId) else { return false } + + return LocationManager.shared.shouldShowLocationWarning(connection: connection) + } + func confirmPressed() { - delegate?.confirmPressed(authorizationId) + delegate?.confirmPressed(authorizationId, apiVersion: apiVersion) } func denyPressed() { - delegate?.denyPressed(authorizationId) + delegate?.denyPressed(authorizationId, apiVersion: apiVersion) + } + + func setFinal(status: AuthorizationStatus) { + guard status.isFinal, + let authStatus = AuthorizationStateView.AuthorizationState(rawValue: status.rawValue) else { return } + + self.status = status + self.state.value = authStatus + self.actionTime = Date() } } diff --git a/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift b/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift index c558fa6d..b179b4f9 100644 --- a/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift +++ b/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift @@ -21,7 +21,7 @@ // import UIKit -import SEAuthenticator +import SEAuthenticatorCore enum AuthorizationsViewModelState: Equatable { case changedConnectionsData @@ -30,10 +30,13 @@ enum AuthorizationsViewModelState: Equatable { case presentFail(String) case scanQrPressed(Bool) case normal + case requestLocationWarning static func == (lhs: AuthorizationsViewModelState, rhs: AuthorizationsViewModelState) -> Bool { switch (lhs, rhs) { - case (.changedConnectionsData, .changedConnectionsData), (.reloadData, .reloadData), (.normal, .normal): return true + case (.changedConnectionsData, .changedConnectionsData), + (.reloadData, .reloadData), (.normal, .normal), + (.requestLocationWarning, .requestLocationWarning): return true case let (.scrollTo(index1), .scrollTo(index2)): return index1 == index2 case let (.presentFail(message1), .presentFail(message2)): return message1 == message2 case let (.scanQrPressed(value1), .scanQrPressed(value2)): return value1 == value2 @@ -53,11 +56,9 @@ class AuthorizationsViewModel { var dataSource: AuthorizationsDataSource! private var connectionsListener: RealmConnectionsListener? - private var poller: SEPoller? + private var poller: SEPoller? // TODO: Think about moving poller to interactor private var connections = ConnectionsCollector.activeConnections - var singleAuthorizationDetailViewModel: AuthorizationDetailViewModel? - var singleAuthorization: (connectionId: String, authorizationId: String)? { willSet { setupPolling() @@ -103,49 +104,74 @@ class AuthorizationsViewModel { } } - func confirmAuthorization(by authorizationId: String) { - guard let data = dataSource.confirmationData(for: authorizationId), - let detailViewModel = dataSource.viewModel(with: authorizationId) else { return } - - detailViewModel.state.value = .processing + func confirmAuthorization(by authorizationId: String, apiVersion: ApiVersion) { + guard let data = dataSource.confirmationData(for: authorizationId, apiVersion: apiVersion), + let detailViewModel = dataSource.viewModel(with: authorizationId, apiVersion: apiVersion) else { return } - AuthorizationsInteractor.confirm( - data: data, - success: { - detailViewModel.state.value = .success - detailViewModel.actionTime = Date() - }, - failure: { _ in - detailViewModel.state.value = .undefined - detailViewModel.actionTime = Date() - } - ) + if detailViewModel.shouldShowLocationWarning { + state.value = .requestLocationWarning + } else { + detailViewModel.state.value = .processing + + AuthorizationsInteractor.confirm( + apiVersion: detailViewModel.apiVersion, + data: data, + successV1: { + self.update(viewModel: detailViewModel, state: .confirmed) + }, + successV2: { response in + if response.status.isFinal { + self.update(viewModel: detailViewModel, state: .confirmed) + } + }, + failure: { _ in + self.update(viewModel: detailViewModel, state: .error) + } + ) + } } - func denyAuthorization(by authorizationId: String) { - guard let data = dataSource.confirmationData(for: authorizationId), - let detailViewModel = dataSource.viewModel(with: authorizationId) else { return } + func denyAuthorization(by authorizationId: String, apiVersion: ApiVersion) { + guard let data = dataSource.confirmationData(for: authorizationId, apiVersion: apiVersion), + let detailViewModel = dataSource.viewModel(with: authorizationId, apiVersion: apiVersion) else { return } - detailViewModel.state.value = .processing + if detailViewModel.shouldShowLocationWarning { + state.value = .requestLocationWarning + } else { + detailViewModel.state.value = .processing + + AuthorizationsInteractor.deny( + apiVersion: detailViewModel.apiVersion, + data: data, + successV1: { + self.update(viewModel: detailViewModel, state: .denied) + }, + successV2: { response in + if response.status.isFinal { + self.update(viewModel: detailViewModel, state: .denied) + } + }, + failure: { _ in + self.update(viewModel: detailViewModel, state: .error) + } + ) + } + } - AuthorizationsInteractor.deny( - data: data, - success: { - detailViewModel.state.value = .denied - detailViewModel.actionTime = Date() - }, - failure: { _ in - detailViewModel.state.value = .undefined - detailViewModel.actionTime = Date() - } - ) + private func update(viewModel: AuthorizationDetailViewModel, state: AuthorizationStateView.AuthorizationState) { + viewModel.state.value = state + viewModel.actionTime = Date() } - private func updateDataSource(with authorizations: [SEAuthorizationData]) { + private func updateDataSource(with authorizations: [SEBaseAuthorizationData]) { if dataSource.update(with: authorizations) { state.value = .reloadData } + scrollToSingleAuthorization() + } + + private func scrollToSingleAuthorization() { if let authorizationToScroll = singleAuthorization { if let detailViewModel = dataSource.viewModel( by: authorizationToScroll.connectionId, @@ -256,10 +282,11 @@ private extension AuthorizationsViewModel { guard let strongSelf = self else { return } DispatchQueue.global(qos: .background).async { - let decryptedAuthorizations = encryptedAuthorizations.compactMap { $0.decryptedAuthorizationData } + let decryptedAuthorizationsV1 = encryptedAuthorizations.compactMap { $0.decryptedAuthorizationData } + let decryptedAuthorizationsV2 = encryptedAuthorizations.compactMap { $0.decryptedAuthorizationDataV2 } DispatchQueue.main.async { - strongSelf.updateDataSource(with: decryptedAuthorizations) + strongSelf.updateDataSource(with: decryptedAuthorizationsV1 + decryptedAuthorizationsV2) } } }, diff --git a/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift b/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift index 1a2b3301..9945b685 100644 --- a/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift +++ b/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift @@ -22,6 +22,7 @@ import Foundation import SEAuthenticator +import SEAuthenticatorCore protocol SingleAuthorizationViewModelEventsDelegate: class { func receivedDetailViewModel(_ detailViewModel: AuthorizationDetailViewModel) @@ -41,7 +42,7 @@ final class SingleAuthorizationViewModel { getAuthorization( connection: connection, authorizationId: authorizationId, - showLocationWarning: locationManagement.showLocationWarning(connection: connection) + showLocationWarning: locationManagement.shouldShowLocationWarning(connection: connection) ) } @@ -52,19 +53,22 @@ final class SingleAuthorizationViewModel { success: { [weak self] encryptedAuthorization in guard let strongSelf = self else { return } - DispatchQueue.global(qos: .background).async { - guard let decryptedAuthorizationData = encryptedAuthorization.decryptedAuthorizationData else { return } + var decryptedAuthorizationData: SEBaseAuthorizationData? - DispatchQueue.main.async { - if let viewModel = AuthorizationDetailViewModel( - decryptedAuthorizationData, - showLocationWarning: showLocationWarning - ) { - strongSelf.detailViewModel = viewModel - strongSelf.detailViewModel?.delegate = self + if connection.isApiV2 { + decryptedAuthorizationData = encryptedAuthorization.decryptedAuthorizationDataV2 + } else { + decryptedAuthorizationData = encryptedAuthorization.decryptedAuthorizationData + } + + guard let data = decryptedAuthorizationData else { return } + + DispatchQueue.main.async { + if let viewModel = AuthorizationDetailViewModel(data, apiVersion: connection.apiVersion) { + strongSelf.detailViewModel = viewModel + strongSelf.detailViewModel?.delegate = self - strongSelf.delegate?.receivedDetailViewModel(viewModel) - } + strongSelf.delegate?.receivedDetailViewModel(viewModel) } } }, @@ -82,7 +86,7 @@ final class SingleAuthorizationViewModel { // MARK: - AuthorizationDetailEventsDelegate extension SingleAuthorizationViewModel: AuthorizationDetailEventsDelegate { - func confirmPressed(_ authorizationId: String) { + func confirmPressed(_ authorizationId: String, apiVersion: ApiVersion) { guard let detailViewModel = detailViewModel, let connection = connection, let url = connection.baseUrl else { return } let confirmData = SEConfirmAuthorizationRequestData( @@ -99,25 +103,23 @@ extension SingleAuthorizationViewModel: AuthorizationDetailEventsDelegate { detailViewModel.state.value = .processing AuthorizationsInteractor.confirm( + apiVersion: detailViewModel.apiVersion, data: confirmData, - success: { - detailViewModel.state.value = .success - detailViewModel.actionTime = Date() - after(finalAuthorizationTimeToLive) { - self.delegate?.shouldClose() + successV1: { + self.updateDetailViewModel(state: .confirmed) + }, + successV2: { response in + if response.status.isFinal { + self.updateDetailViewModel(state: .confirmed) } }, failure: { _ in - detailViewModel.state.value = .undefined - detailViewModel.actionTime = Date() - after(finalAuthorizationTimeToLive) { - self.delegate?.shouldClose() - } + self.updateDetailViewModel(state: .error) } ) } - func denyPressed(_ authorizationId: String) { + func denyPressed(_ authorizationId: String, apiVersion: ApiVersion) { guard let detailViewModel = detailViewModel, let connection = connection, let url = connection.baseUrl else { return } let confirmData = SEConfirmAuthorizationRequestData( @@ -134,26 +136,32 @@ extension SingleAuthorizationViewModel: AuthorizationDetailEventsDelegate { detailViewModel.state.value = .processing AuthorizationsInteractor.deny( + apiVersion: detailViewModel.apiVersion, data: confirmData, - success: { - detailViewModel.state.value = .denied - detailViewModel.actionTime = Date() - after(finalAuthorizationTimeToLive) { - self.delegate?.shouldClose() + successV1: { + self.updateDetailViewModel(state: .denied) + }, + successV2: { response in + if response.status.isFinal { + self.updateDetailViewModel(state: .denied) } }, failure: { _ in - detailViewModel.state.value = .undefined - detailViewModel.actionTime = Date() - after(finalAuthorizationTimeToLive) { - self.delegate?.shouldClose() - } + self.updateDetailViewModel(state: .error) } ) } func authorizationExpired() { - detailViewModel?.state.value = .expired + detailViewModel?.state.value = .timeOut + after(finalAuthorizationTimeToLive) { + self.delegate?.shouldClose() + } + } + + private func updateDetailViewModel(state: AuthorizationStateView.AuthorizationState) { + detailViewModel?.state.value = state + detailViewModel?.actionTime = Date() after(finalAuthorizationTimeToLive) { self.delegate?.shouldClose() } diff --git a/Example/Authenticator/View Models/Connections/ConnectViewModel.swift b/Example/Authenticator/View Models/Connections/ConnectViewModel.swift new file mode 100644 index 00000000..299c00ec --- /dev/null +++ b/Example/Authenticator/View Models/Connections/ConnectViewModel.swift @@ -0,0 +1,47 @@ +// +// ConnectViewModel +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation + +protocol ConnectViewModelEventsDelegate: class { + func showNoInternetConnectionAlert() +} + +final class ConnectViewModel { + weak var delegate: ConnectViewModelEventsDelegate? + + private let reachabilityManager: Connectable + + init(reachabilityManager: Connectable) { + self.reachabilityManager = reachabilityManager + } +} + +// MARK: - Delegate actions +extension ConnectViewModel { + func checkInternetConnection() { + guard reachabilityManager.isConnected else { + delegate?.showNoInternetConnectionAlert() + return + } + } +} diff --git a/Example/Authenticator/View Models/Connections/ConnectionCellViewModel.swift b/Example/Authenticator/View Models/Connections/ConnectionCellViewModel.swift index 1e57ec62..82ef3e76 100644 --- a/Example/Authenticator/View Models/Connections/ConnectionCellViewModel.swift +++ b/Example/Authenticator/View Models/Connections/ConnectionCellViewModel.swift @@ -24,6 +24,7 @@ import UIKit protocol ConnectionCellEventsDelegate: class { func renamePressed(id: String) + func accessLocationPressed() func supportPressed(email: String) func deletePressed(id: String, showConfirmation: Bool) func reconnectPreseed(id: String) @@ -44,9 +45,16 @@ class ConnectionCellViewModel { } var description: NSAttributedString { - return connectionStatus.value == .inactive - ? buildInactiveDescription() - : buildActiveDescription() + if LocationManager.shared.shouldShowLocationWarning(connection: connection) { + return NSAttributedString( + string: l10n(.grantAccessToLocationServices), + attributes: [NSAttributedString.Key.foregroundColor: UIColor.descriptionYellow] + ) + } else { + return connectionStatus.value == .inactive + ? buildInactiveDescription() + : buildActiveDescription() + } } var hasConsents: Bool { @@ -88,21 +96,42 @@ class ConnectionCellViewModel { }) if self?.hasConsents == true { - actions.append(UIAction(title: l10n(.viewConsents), image: UIImage(systemName: "doc.plaintext")) { _ in - strongSelf.delegate?.consentsPressed(id: strongSelf.connection.id) - }) + actions.append( + UIAction(title: l10n(.viewConsents), image: UIImage(systemName: "doc.plaintext")) { _ in + strongSelf.delegate?.consentsPressed(id: strongSelf.connection.id) + } + ) + } + if LocationManager.shared.shouldShowLocationWarning(connection: self?.connection) { + actions.append( + UIAction(title: l10n(.accessToLocation), image: UIImage(systemName: "mappin.and.ellipse")) { _ in + strongSelf.delegate?.accessLocationPressed() + } + ) } if self?.connection.status == ConnectionStatus.inactive.rawValue { - actions.append(UIAction(title: l10n(.remove), image: UIImage(systemName: "xmark")) { _ in - strongSelf.delegate?.deletePressed(id: strongSelf.connection.id, showConfirmation: false) - }) + actions.append( + UIAction(title: l10n(.remove), image: UIImage(systemName: "xmark")) { _ in + strongSelf.delegate?.deletePressed(id: strongSelf.connection.id, showConfirmation: false) + } + ) } else { - actions.append(UIAction(title: l10n(.delete), image: UIImage(systemName: "trash")) { _ in - strongSelf.delegate?.deletePressed(id: strongSelf.connection.id, showConfirmation: true) - }) + actions.append( + UIAction(title: l10n(.delete), image: UIImage(systemName: "trash")) { _ in + strongSelf.delegate?.deletePressed(id: strongSelf.connection.id, showConfirmation: true) + } + ) } + actions.append( + UIAction( + title: "\(l10n(.id)) \(strongSelf.connection.id)", + image: UIImage(systemName: "info.circle"), + attributes: .disabled + ) { _ in return } + ) + return UIMenu(title: "", image: nil, identifier: nil, options: .destructive, children: actions) } return configuration diff --git a/Example/Authenticator/View Models/Connections/ConnectionPickerViewModel.swift b/Example/Authenticator/View Models/Connections/ConnectionPickerViewModel.swift index ff18fdbe..417807a4 100644 --- a/Example/Authenticator/View Models/Connections/ConnectionPickerViewModel.swift +++ b/Example/Authenticator/View Models/Connections/ConnectionPickerViewModel.swift @@ -22,10 +22,18 @@ import UIKit +struct SubmitActionData { + var baseUrl: URL + var connectionGuid: String + var connectionId: String + var accessToken: String + var apiVersion: String +} + final class ConnectionPickerViewModel { private var connections: [Connection] - var selectedConnectionClosure: ((String, String) -> ())? + var selectedConnectionClosure: ((SubmitActionData) -> ())? init(connections: [Connection]) { self.connections = connections @@ -41,6 +49,17 @@ final class ConnectionPickerViewModel { func selectedConnection(at indexPath: IndexPath) { let viewModel = ConnectionCellViewModel(connection: connections[indexPath.row]) - selectedConnectionClosure?(viewModel.guid, viewModel.accessToken) + + guard let baseUrl = viewModel.baseUrl else { return } + + selectedConnectionClosure?( + SubmitActionData( + baseUrl: baseUrl, + connectionGuid: viewModel.guid, + connectionId: viewModel.id, + accessToken: viewModel.accessToken, + apiVersion: viewModel.apiVersion + ) + ) } } diff --git a/Example/Authenticator/View Models/Connections/ConnectionsViewModel.swift b/Example/Authenticator/View Models/Connections/ConnectionsViewModel.swift index 3dea9154..66523f67 100644 --- a/Example/Authenticator/View Models/Connections/ConnectionsViewModel.swift +++ b/Example/Authenticator/View Models/Connections/ConnectionsViewModel.swift @@ -23,6 +23,7 @@ import Foundation import RealmSwift import SEAuthenticator +import SEAuthenticatorCore protocol ConnectionsEventsDelegate: class { func showEditConnectionAlert(placeholder: String, completion: @escaping (String) -> ()) @@ -32,6 +33,9 @@ protocol ConnectionsEventsDelegate: class { func consentsPressed(connectionId: String, consents: [SEConsentData]) func updateViews() func addPressed() + func presentError(_ error: String) + func showNoInternetConnectionAlert(completion: @escaping () -> Void) + func showDeleteConfirmationAlert(completion: @escaping () -> Void) } final class ConnectionsViewModel { @@ -43,7 +47,10 @@ final class ConnectionsViewModel { private var connectionsNotificationToken: NotificationToken? private var connectionsListener: RealmConnectionsListener? - init() { + private let reachabilityManager: Connectable + + init(reachabilityManager: Connectable) { + self.reachabilityManager = reachabilityManager connectionsListener = RealmConnectionsListener( onDataChange: { self.delegate?.updateViews() @@ -62,7 +69,7 @@ final class ConnectionsViewModel { var emptyViewData: EmptyViewData { return EmptyViewData( - image: #imageLiteral(resourceName: "noConnections"), + image: UIImage(named: "noConnections", in: .authenticator_main, compatibleWith: nil) ?? UIImage(), title: l10n(.noConnections), description: l10n(.noConnectionsDescription), buttonTitle: l10n(.connect) @@ -89,8 +96,19 @@ final class ConnectionsViewModel { delegate?.updateViews() } - private func remove(connection: Connection) { - ConnectionsInteractor.revoke(connection) + private func revoke(connection: Connection, interactor: BaseConnectionsInteractor) { + interactor.revoke( + connection, + success: { }, + failure: { error in + self.delegate?.presentError(error) + } + ) + deleteConnection(connection: connection) + } + + private func deleteConnection(connection: Connection) { + SECryptoHelper.deleteKeyPair(with: connection.providerPublicKeyTag) SECryptoHelper.deleteKeyPair(with: SETagHelper.create(for: connection.guid)) ConnectionRepository.delete(connection) } @@ -112,13 +130,19 @@ extension ConnectionsViewModel { func remove(at indexPath: IndexPath) { guard let connection = item(for: indexPath) else { return } - remove(connection: connection) + revoke( + connection: connection, + interactor: connection.isApiV2 ? ConnectionsInteractorV2() : ConnectionsInteractor() + ) } func remove(by id: ID) { guard let connection = ConnectionsCollector.with(id: id) else { return } - remove(connection: connection) + revoke( + connection: connection, + interactor: connection.isApiV2 ? ConnectionsInteractorV2() : ConnectionsInteractor() + ) } func updateName(by id: ID) { @@ -151,6 +175,21 @@ extension ConnectionsViewModel { func reconnect(id: ID) { delegate?.reconnect(by: id) } + + func checkInternetAndRemoveConnection(id: String, showConfirmation: Bool) { + guard reachabilityManager.isConnected else { + delegate?.showNoInternetConnectionAlert { + if self.reachabilityManager.isConnected { self.remove(by: id) } + } + return + } + + if showConfirmation { + delegate?.showDeleteConfirmationAlert { self.remove(by: id) } + } else { + remove(by: id) + } + } } // MARK: - Actions diff --git a/Example/Authenticator/View Models/Connections/ConsentDetailViewModel.swift b/Example/Authenticator/View Models/Connections/ConsentDetailViewModel.swift index f3abf26a..9ef9eb67 100644 --- a/Example/Authenticator/View Models/Connections/ConsentDetailViewModel.swift +++ b/Example/Authenticator/View Models/Connections/ConsentDetailViewModel.swift @@ -21,7 +21,7 @@ // import UIKit -import SEAuthenticator +import SEAuthenticatorCore protocol ConsentDetailViewModelEventsDelegate: class { func revoke(_ consent: SEConsentData, messageTitle: String, messageDescription: String, successMessage: String) @@ -45,7 +45,7 @@ final class ConsentDetailViewModel { } var expiresInText: String { - return "\(consent.expiresAt.get(.day)) \(l10n(.daysLeft))" + return "\(consent.expiresAt.numberOfDaysFromNow) \(l10n(.daysLeft))" } var descriptionAtributedString: NSMutableAttributedString { diff --git a/Example/Authenticator/View Models/Connections/ConsentViewModel.swift b/Example/Authenticator/View Models/Connections/ConsentViewModel.swift index a97a476f..d8fb8106 100644 --- a/Example/Authenticator/View Models/Connections/ConsentViewModel.swift +++ b/Example/Authenticator/View Models/Connections/ConsentViewModel.swift @@ -22,6 +22,7 @@ import Foundation import SEAuthenticator +import SEAuthenticatorCore enum ConsentType: String { case aisp @@ -72,7 +73,7 @@ final class ConsentsViewModel { let expirationAttributedString = NSMutableAttributedString() - let numberOfDaysToExpire = consent.expiresAt.get(.day) + let numberOfDaysToExpire = consent.expiresAt.numberOfDaysFromNow let expiresInString = numberOfDaysToExpire == 1 ? "\(numberOfDaysToExpire) \(l10n(.day))" : "\(numberOfDaysToExpire) \(l10n(.days))" let expiresInAttributedMessage = NSMutableAttributedString( diff --git a/Example/Authenticator/Views/Authorizations/AuthorizationContentDynamicStackView.swift b/Example/Authenticator/Views/Authorizations/AuthorizationContentDynamicStackView.swift new file mode 100644 index 00000000..570443bf --- /dev/null +++ b/Example/Authenticator/Views/Authorizations/AuthorizationContentDynamicStackView.swift @@ -0,0 +1,149 @@ +// +// AuthorizationContentDynamicStackView +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import UIKit +import SEAuthenticatorCore + +private enum FieldType: String { + case payment + case extra + case text + + var textColor: UIColor { + switch self { + case .payment: return .dark80_grey100 + case .extra: return .extraTextColor + case .text: return .titleColor + } + } +} + +/* + The UIStackView which is designed to construct the authorization content dynamically. + */ +final class AuthorizationContentDynamicStackView: UIStackView { + private let paymentSortedKeys = [ + SENetKeys.payee, SENetKeys.amount, SENetKeys.account, + SENetKeys.paymentDate, SENetKeys.reference, SENetKeys.fee, SENetKeys.exchangeRate + ] + private let extraSortedKeys = [SENetKeys.actionDate, SENetKeys.device, SENetKeys.location, SENetKeys.ip] + + init() { + super.init(frame: .zero) + axis = .vertical + alignment = .fill + distribution = .fillProportionally + spacing = 8.0 + } + + /* + Setup the stackView content using the authorization description attributes. + + The content will be constructed using next attributes: + - payment + - text + - extra + + - parameters: + - attributes: Authorization description dictionary with nested attributes dictionaries + */ + func setup(using attributes: [String: Any]) { + removeAllArrangedSubviews() + + if let paymentDict = attributes[SENetKeys.payment] as? [String: Any] { + paymentSortedKeys.forEach { + addArrangedSubview(inputDict: paymentDict, key: $0, type: .payment) + } + } + if let text = attributes[SENetKeys.text] as? String { + let label = UILabel( + font: .systemFont(ofSize: 16.0, weight: .regular), + alignment: .left, + textColor: FieldType.text.textColor + ) + label.text = text + addArrangedSubview(label) + } + if let extraDict = attributes[SENetKeys.extra] as? [String: Any] { + // Adding empty view as separator between blocks + let emptyView = UIView() + emptyView.height(16.0) + addArrangedSubview(emptyView) + + extraSortedKeys.forEach { + addArrangedSubview(inputDict: extraDict, key: $0, type: .extra) + } + } + } + + private func addArrangedSubview(inputDict: [String: Any]?, key: String, type: FieldType) { + if let value = inputDict?[key] as? String, !value.isEmpty { + addArrangedSubview(contentView(title: key, description: value, fieldType: type)) + } + } + + private func contentView(title: String? = nil, description: String, fieldType: FieldType) -> UIView { + let contentView = UIView() + let contentTitleLabel = UILabel( + font: .systemFont(ofSize: 16.0, weight: .regular), + textColor: fieldType.textColor + ) + let descriptionLabel = UILabel( + font: .systemFont(ofSize: 16.0, weight: .regular), + alignment: .right, + textColor: fieldType == .payment ? .titleColor : FieldType.extra.textColor + ) + + var title = title?.replacingOccurrences(of: "_", with: " ").capitalizingFirstLetter() ?? "" + + if fieldType == .extra { + if title == SENetKeys.ip.capitalizingFirstLetter() { + title = l10n(.ipAddress) + } + + title += ":" + } + + contentTitleLabel.text = title + descriptionLabel.text = description + + contentView.addSubviews(contentTitleLabel, descriptionLabel) + + contentTitleLabel.leftToSuperview() + contentTitleLabel.centerYToSuperview() + descriptionLabel.centerYToSuperview() + + if fieldType == .payment { + descriptionLabel.leftToRight(of: contentTitleLabel, offset: 16.0, relation: .equalOrGreater) + descriptionLabel.rightToSuperview(offset: -16.0) + } else { + descriptionLabel.leftToRight(of: contentTitleLabel, offset: 6.0) + } + contentView.height(fieldType == .payment ? 24.0 : 18.0) + + return contentView + } + + required init(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Example/Authenticator/Views/Authorizations/AuthorizationContentView.swift b/Example/Authenticator/Views/Authorizations/AuthorizationContentView.swift index 5ae77eae..604d7f85 100644 --- a/Example/Authenticator/Views/Authorizations/AuthorizationContentView.swift +++ b/Example/Authenticator/Views/Authorizations/AuthorizationContentView.swift @@ -44,6 +44,7 @@ final class AuthorizationContentView: UIView { }() private lazy var descriptionTextView = UITextView() + private lazy var attributesStackView = AuthorizationContentDynamicStackView() private lazy var webView: WKWebView = { let webView = WKWebView(frame: .zero, configuration: WKWebViewConfiguration()) webView.layer.masksToBounds = true @@ -54,6 +55,7 @@ final class AuthorizationContentView: UIView { let stackView = UIStackView() stackView.axis = .vertical stackView.spacing = Layout.sideOffset + stackView.distribution = .fillProportionally return stackView }() private var buttonsStackView: UIStackView = { @@ -64,38 +66,25 @@ final class AuthorizationContentView: UIView { stackView.spacing = 11.0 return stackView }() - private let locationWarningLabel = UILabel(font: .systemFont(ofSize: 18.0, weight: .regular), textColor: .redAlert) var viewModel: AuthorizationDetailViewModel! { didSet { titleLabel.text = viewModel.title - if viewModel.showLocationWarning { - locationWarningLabel.text = l10n(.locationWarning) - } - buttonsStackView.isHidden = viewModel.showLocationWarning - locationWarningLabel.isHidden = !viewModel.showLocationWarning - guard viewModel.state.value == .base else { stateView.set(state: viewModel.state.value) return } - if viewModel.expired && viewModel.state.value != .expired { - stateView.set(state: .expired) + if viewModel.expired && viewModel.state.value != .timeOut { + stateView.set(state: .timeOut) } else { stateView.set(state: .base) - if viewModel.description.htmlToAttributedString != nil { - let supportDarkCSS = "" - - contentStackView.removeArrangedSubview(descriptionTextView) - webView.loadHTMLString(viewModel.description + supportDarkCSS, baseURL: nil) - contentStackView.addArrangedSubview(webView) + if viewModel.apiVersion == "1" { + setupContentV1() } else { - contentStackView.removeArrangedSubview(webView) - descriptionTextView.text = viewModel.description - contentStackView.addArrangedSubview(descriptionTextView) + setupContentV2(using: viewModel.descriptionAttributes) } } @@ -116,6 +105,36 @@ final class AuthorizationContentView: UIView { layout() } + private func setupContentV1() { + if viewModel.description.htmlToAttributedString != nil { + setupWebView(content: viewModel.description) + } else { + contentStackView.removeArrangedSubview(webView) + descriptionTextView.text = viewModel.description + contentStackView.addArrangedSubview(descriptionTextView) + } + } + + private func setupContentV2(using attributes: [String: Any]) { + if let html = attributes["html"] as? String { + setupWebView(content: html) + } else { + contentStackView.removeAllArrangedSubviews() + + let attributesScrollView = UIScrollView() + attributesScrollView.addSubview(attributesStackView) + + contentStackView.addArrangedSubview(attributesScrollView) + + attributesScrollView.edgesToSuperview() + + attributesStackView.width(to: attributesScrollView) + attributesStackView.edgesToSuperview() + + attributesStackView.setup(using: attributes) + } + } + required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -132,6 +151,14 @@ private extension AuthorizationContentView { buttonsStackView.addArrangedSubviews(leftButton, rightButton) } + + func setupWebView(content: String) { + let supportDarkCSS = "" + + contentStackView.removeAllArrangedSubviews() + webView.loadHTMLString(content + supportDarkCSS, baseURL: nil) + contentStackView.addArrangedSubview(webView) + } } // MARK: - Actions @@ -148,7 +175,7 @@ private extension AuthorizationContentView { // MARK: - Layout extension AuthorizationContentView: Layoutable { func layout() { - addSubviews(titleLabel, contentStackView, buttonsStackView, locationWarningLabel, stateView) + addSubviews(titleLabel, contentStackView, buttonsStackView, stateView) titleLabel.top(to: self, offset: Layout.titleLabelTopOffset) titleLabel.centerX(to: self) @@ -167,11 +194,6 @@ extension AuthorizationContentView: Layoutable { buttonsStackView.bottom(to: self, safeAreaLayoutGuide.bottomAnchor, offset: -Layout.bottomOffset) buttonsStackView.centerXToSuperview() - locationWarningLabel.leftToSuperview(offset: Layout.sideOffset) - locationWarningLabel.rightToSuperview(offset: -Layout.sideOffset) - locationWarningLabel.bottom(to: self, safeAreaLayoutGuide.bottomAnchor, offset: -Layout.bottomOffset) - locationWarningLabel.centerXToSuperview() - stateView.edgesToSuperview() } } diff --git a/Example/Authenticator/Views/Authorizations/AuthorizationStateView.swift b/Example/Authenticator/Views/Authorizations/AuthorizationStateView.swift index 31f7be41..9d33ca72 100644 --- a/Example/Authenticator/Views/Authorizations/AuthorizationStateView.swift +++ b/Example/Authenticator/Views/Authorizations/AuthorizationStateView.swift @@ -38,18 +38,20 @@ final class AuthorizationStateView: UIView { enum AuthorizationState: String, Equatable { case base case denied - case expired case processing - case success + case confirmed + case error + case timeOut + case unavailable case undefined var title: String { switch self { case .processing: return l10n(.processing) - case .success: return l10n(.successfulAuthorization) - case .expired: return l10n(.timeOut) + case .confirmed: return l10n(.successfulAuthorization) + case .timeOut: return l10n(.timeOut) case .denied: return l10n(.denied) - case .undefined: return l10n(.somethingWentWrong) + case .error, .unavailable: return l10n(.somethingWentWrong) default: return "" } } @@ -57,28 +59,29 @@ final class AuthorizationStateView: UIView { var message: String { switch self { case .processing: return l10n(.activeMessage) - case .success: return l10n(.successfulAuthorizationMessage) - case .expired: return l10n(.timeOutMessage) + case .confirmed: return l10n(.successfulAuthorizationMessage) + case .timeOut: return l10n(.timeOutMessage) case .denied: return l10n(.deniedMessage) - case .undefined: return l10n(.errorOccuredPleaseTryAgain) + case .error, .unavailable: return l10n(.errorOccuredPleaseTryAgain) default: return "" } } var topAccessoryView: UIView { switch self { - case .success: return AspectFitImageView(imageName: "success") - case .expired: return AspectFitImageView(imageName: "timeout") + case .confirmed: return AspectFitImageView(imageName: "success") + case .timeOut: return AspectFitImageView(imageName: "timeout") case .denied: return AspectFitImageView(imageName: "deny") - case .undefined: return AspectFitImageView(imageName: "smth_wrong") + case .error, .unavailable: return AspectFitImageView(imageName: "smth_wrong") default: return LoadingIndicatorView() } } static func == (lhs: AuthorizationState, rhs: AuthorizationState) -> Bool { switch (lhs, rhs) { - case (.base, .base), (.denied, .denied), (.expired, .expired), - (.processing, .processing), (.success, .success), (.undefined, .undefined): return true + case (.base, .base), (.denied, .denied), (.processing, .processing), + (.confirmed, .confirmed), (.timeOut, .timeOut), + (.error, .error), (.unavailable, .unavailable) : return true default: return false } } diff --git a/Example/Authenticator/Views/Authorizations/AuthorizationView.swift b/Example/Authenticator/Views/Authorizations/AuthorizationView.swift index d0603fb1..c78f85d1 100644 --- a/Example/Authenticator/Views/Authorizations/AuthorizationView.swift +++ b/Example/Authenticator/Views/Authorizations/AuthorizationView.swift @@ -21,6 +21,7 @@ // import UIKit +import SEAuthenticatorCore private struct Layout { static let headerSpacing: CGFloat = 16.0 @@ -192,12 +193,12 @@ extension AuthorizationView: UICollectionViewDelegate, UICollectionViewDelegateF // MARK: - AuthorizationCellDelegate extension AuthorizationView: AuthorizationDetailEventsDelegate { - func confirmPressed(_ authorizationId: String) { - viewModel.confirmAuthorization(by: authorizationId) + func confirmPressed(_ authorizationId: String, apiVersion: ApiVersion) { + viewModel.confirmAuthorization(by: authorizationId, apiVersion: apiVersion) } - func denyPressed(_ authorizationId: String) { - viewModel.denyAuthorization(by: authorizationId) + func denyPressed(_ authorizationId: String, apiVersion: ApiVersion) { + viewModel.denyAuthorization(by: authorizationId, apiVersion: apiVersion) } func authorizationExpired() {} diff --git a/Example/Authenticator/Views/Consents/ConsentSharedDataView.swift b/Example/Authenticator/Views/Consents/ConsentSharedDataView.swift index a0c16361..83879362 100644 --- a/Example/Authenticator/Views/Consents/ConsentSharedDataView.swift +++ b/Example/Authenticator/Views/Consents/ConsentSharedDataView.swift @@ -21,7 +21,7 @@ // import UIKit -import SEAuthenticator +import SEAuthenticatorCore private struct Layout { static let labelsSpacing: CGFloat = 10.0 diff --git a/Example/Podfile b/Example/Podfile index 0eebad25..13132ec6 100644 --- a/Example/Podfile +++ b/Example/Podfile @@ -2,7 +2,9 @@ platform :ios, '10.0' use_frameworks! def shared_pods + pod 'SaltedgeAuthenticatorCore', :path => '../SaltedgeAuthenticatorCore' pod 'SaltedgeAuthenticatorSDK', :path => '../' + pod 'SaltedgeAuthenticatorSDKv2', :path => '../SaltedgeAuthenticatorSDKv2' pod 'ReachabilitySwift' pod 'TinyConstraints' pod 'RealmSwift', '5.5.1', :inhibit_warnings => true @@ -26,8 +28,8 @@ post_install do |pi| pi.pods_project.targets.each do |t| t.build_configurations.each do |config| config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '10.0' - config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64' - config.build_settings['CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER'] = 'NO' + config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64' + config.build_settings['CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER'] = 'NO' end end end diff --git a/Example/Podfile.lock b/Example/Podfile.lock index bea6b737..9e0eda50 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -1,98 +1,98 @@ PODS: - - CryptoSwift (1.4.0) - - Firebase/Analytics (7.11.0): + - CryptoSwift (1.4.1) + - Firebase/Analytics (8.4.0): - Firebase/Core - - Firebase/Core (7.11.0): + - Firebase/Core (8.4.0): - Firebase/CoreOnly - - FirebaseAnalytics (~> 7.11.0) - - Firebase/CoreOnly (7.11.0): - - FirebaseCore (= 7.11.0) - - Firebase/Crashlytics (7.11.0): + - FirebaseAnalytics (~> 8.4.0) + - Firebase/CoreOnly (8.4.0): + - FirebaseCore (= 8.4.0) + - Firebase/Crashlytics (8.4.0): - Firebase/CoreOnly - - FirebaseCrashlytics (~> 7.11.0) - - FirebaseAnalytics (7.11.0): - - FirebaseAnalytics/AdIdSupport (= 7.11.0) - - FirebaseCore (~> 7.0) - - FirebaseInstallations (~> 7.0) - - GoogleUtilities/AppDelegateSwizzler (~> 7.0) - - GoogleUtilities/MethodSwizzler (~> 7.0) - - GoogleUtilities/Network (~> 7.0) - - "GoogleUtilities/NSData+zlib (~> 7.0)" + - FirebaseCrashlytics (~> 8.4.0) + - FirebaseAnalytics (8.4.0): + - FirebaseAnalytics/AdIdSupport (= 8.4.0) + - FirebaseCore (~> 8.0) + - FirebaseInstallations (~> 8.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.4) + - GoogleUtilities/MethodSwizzler (~> 7.4) + - GoogleUtilities/Network (~> 7.4) + - "GoogleUtilities/NSData+zlib (~> 7.4)" - nanopb (~> 2.30908.0) - - FirebaseAnalytics/AdIdSupport (7.11.0): - - FirebaseAnalytics/Base (= 7.11.0) - - FirebaseCore (~> 7.0) - - FirebaseInstallations (~> 7.0) - - GoogleAppMeasurement/AdIdSupport (= 7.11.0) - - GoogleUtilities/AppDelegateSwizzler (~> 7.0) - - GoogleUtilities/MethodSwizzler (~> 7.0) - - GoogleUtilities/Network (~> 7.0) - - "GoogleUtilities/NSData+zlib (~> 7.0)" + - FirebaseAnalytics/AdIdSupport (8.4.0): + - FirebaseCore (~> 8.0) + - FirebaseInstallations (~> 8.0) + - GoogleAppMeasurement (= 8.4.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.4) + - GoogleUtilities/MethodSwizzler (~> 7.4) + - GoogleUtilities/Network (~> 7.4) + - "GoogleUtilities/NSData+zlib (~> 7.4)" - nanopb (~> 2.30908.0) - - FirebaseAnalytics/Base (7.11.0): - - FirebaseCore (~> 7.0) - - FirebaseInstallations (~> 7.0) - - GoogleUtilities/AppDelegateSwizzler (~> 7.0) - - GoogleUtilities/MethodSwizzler (~> 7.0) - - GoogleUtilities/Network (~> 7.0) - - "GoogleUtilities/NSData+zlib (~> 7.0)" + - FirebaseCore (8.4.0): + - FirebaseCoreDiagnostics (~> 8.0) + - GoogleUtilities/Environment (~> 7.4) + - GoogleUtilities/Logger (~> 7.4) + - FirebaseCoreDiagnostics (8.4.0): + - GoogleDataTransport (~> 9.0) + - GoogleUtilities/Environment (~> 7.4) + - GoogleUtilities/Logger (~> 7.4) - nanopb (~> 2.30908.0) - - FirebaseCore (7.11.0): - - FirebaseCoreDiagnostics (~> 7.4) - - GoogleUtilities/Environment (~> 7.0) - - GoogleUtilities/Logger (~> 7.0) - - FirebaseCoreDiagnostics (7.11.0): - - GoogleDataTransport (~> 8.4) - - GoogleUtilities/Environment (~> 7.0) - - GoogleUtilities/Logger (~> 7.0) + - FirebaseCrashlytics (8.4.0): + - FirebaseCore (~> 8.0) + - FirebaseInstallations (~> 8.0) + - GoogleDataTransport (~> 9.0) + - GoogleUtilities/Environment (~> 7.4) - nanopb (~> 2.30908.0) - - FirebaseCrashlytics (7.11.0): - - FirebaseCore (~> 7.0) - - FirebaseInstallations (~> 7.0) - - GoogleDataTransport (~> 8.4) + - PromisesObjC (< 3.0, >= 1.2) + - FirebaseInstallations (8.4.0): + - FirebaseCore (~> 8.0) + - GoogleUtilities/Environment (~> 7.4) + - GoogleUtilities/UserDefaults (~> 7.4) + - PromisesObjC (< 3.0, >= 1.2) + - GoogleAppMeasurement (8.4.0): + - GoogleAppMeasurement/AdIdSupport (= 8.4.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.4) + - GoogleUtilities/MethodSwizzler (~> 7.4) + - GoogleUtilities/Network (~> 7.4) + - "GoogleUtilities/NSData+zlib (~> 7.4)" - nanopb (~> 2.30908.0) - - PromisesObjC (~> 1.2) - - FirebaseInstallations (7.11.0): - - FirebaseCore (~> 7.0) - - GoogleUtilities/Environment (~> 7.0) - - GoogleUtilities/UserDefaults (~> 7.0) - - PromisesObjC (~> 1.2) - - GoogleAppMeasurement/AdIdSupport (7.11.0): - - GoogleUtilities/AppDelegateSwizzler (~> 7.0) - - GoogleUtilities/MethodSwizzler (~> 7.0) - - GoogleUtilities/Network (~> 7.0) - - "GoogleUtilities/NSData+zlib (~> 7.0)" + - GoogleAppMeasurement/AdIdSupport (8.4.0): + - GoogleUtilities/AppDelegateSwizzler (~> 7.4) + - GoogleUtilities/MethodSwizzler (~> 7.4) + - GoogleUtilities/Network (~> 7.4) + - "GoogleUtilities/NSData+zlib (~> 7.4)" - nanopb (~> 2.30908.0) - - GoogleDataTransport (8.4.0): + - GoogleDataTransport (9.1.0): - GoogleUtilities/Environment (~> 7.2) - nanopb (~> 2.30908.0) - - PromisesObjC (~> 1.2) - - GoogleUtilities/AppDelegateSwizzler (7.4.0): + - PromisesObjC (< 3.0, >= 1.2) + - GoogleUtilities/AppDelegateSwizzler (7.5.0): - GoogleUtilities/Environment - GoogleUtilities/Logger - GoogleUtilities/Network - - GoogleUtilities/Environment (7.4.0): - - PromisesObjC (~> 1.2) - - GoogleUtilities/Logger (7.4.0): + - GoogleUtilities/Environment (7.5.0): + - PromisesObjC (< 3.0, >= 1.2) + - GoogleUtilities/Logger (7.5.0): - GoogleUtilities/Environment - - GoogleUtilities/MethodSwizzler (7.4.0): + - GoogleUtilities/MethodSwizzler (7.5.0): - GoogleUtilities/Logger - - GoogleUtilities/Network (7.4.0): + - GoogleUtilities/Network (7.5.0): - GoogleUtilities/Logger - "GoogleUtilities/NSData+zlib" - GoogleUtilities/Reachability - - "GoogleUtilities/NSData+zlib (7.4.0)" - - GoogleUtilities/Reachability (7.4.0): + - "GoogleUtilities/NSData+zlib (7.5.0)" + - GoogleUtilities/Reachability (7.5.0): - GoogleUtilities/Logger - - GoogleUtilities/UserDefaults (7.4.0): + - GoogleUtilities/UserDefaults (7.5.0): - GoogleUtilities/Logger + - JOSESwift (2.4.0) - nanopb (2.30908.0): - nanopb/decode (= 2.30908.0) - nanopb/encode (= 2.30908.0) - nanopb/decode (2.30908.0) - nanopb/encode (2.30908.0) - Nimble (9.2.0) - - PromisesObjC (1.2.12) + - PromisesObjC (2.0.0) - Quick (4.0.0) - ReachabilitySwift (5.0.0) - Realm (5.5.1): @@ -100,8 +100,14 @@ PODS: - Realm/Headers (5.5.1) - RealmSwift (5.5.1): - Realm (= 5.5.1) - - SaltedgeAuthenticatorSDK (1.1.1): + - SaltedgeAuthenticatorCore (1.1.0): - CryptoSwift + - SaltedgeAuthenticatorSDK (1.1.0): + - CryptoSwift + - SaltedgeAuthenticatorCore + - SaltedgeAuthenticatorSDKv2 (1.1.0): + - JOSESwift + - SaltedgeAuthenticatorCore - SDWebImage (5.10.4): - SDWebImage/Core (= 5.10.4) - SDWebImage/Core (5.10.4) @@ -115,7 +121,9 @@ DEPENDENCIES: - Quick - ReachabilitySwift - RealmSwift (= 5.5.1) + - SaltedgeAuthenticatorCore (from `../SaltedgeAuthenticatorCore`) - SaltedgeAuthenticatorSDK (from `../`) + - SaltedgeAuthenticatorSDKv2 (from `../SaltedgeAuthenticatorSDKv2`) - SDWebImage (= 5.10.4) - TinyConstraints - Valet (~> 3.2.8) @@ -132,6 +140,7 @@ SPEC REPOS: - GoogleAppMeasurement - GoogleDataTransport - GoogleUtilities + - JOSESwift - nanopb - Nimble - PromisesObjC @@ -144,32 +153,39 @@ SPEC REPOS: - Valet EXTERNAL SOURCES: + SaltedgeAuthenticatorCore: + :path: "../SaltedgeAuthenticatorCore" SaltedgeAuthenticatorSDK: :path: "../" + SaltedgeAuthenticatorSDKv2: + :path: "../SaltedgeAuthenticatorSDKv2" SPEC CHECKSUMS: - CryptoSwift: 7cc902df1784de3b389a387756c7d710f197730c - Firebase: c121feb35e4126c0b355e3313fa9b487d47319fd - FirebaseAnalytics: cd3bd84d722a24a8923918af8af8e5236f615d77 - FirebaseCore: 907447d8917a4d3eb0cce2829c5a0ad21d90b432 - FirebaseCoreDiagnostics: 68ad972f99206cef818230f3f3179d52ccfb7f8c - FirebaseCrashlytics: 272b675aa9d1e9bae1f9e1449fcc1f2cf6042806 - FirebaseInstallations: a58d4f72ec5861840b84df489f2668d970df558a - GoogleAppMeasurement: fd19169c3034975cb934e865e5667bfdce59df7f - GoogleDataTransport: cd9db2180fcecd8da1b561aea31e3e56cf834aa7 - GoogleUtilities: 284cddc7fffc14ae1907efb6f78ab95c1fccaedc + CryptoSwift: 0bc800a7e6a24c4fc9ebeab97d44b0d5f73a78bd + Firebase: 54cdc8bc9c9b3de54f43dab86e62f5a76b47034f + FirebaseAnalytics: 4751d6a49598a2b58da678cc07df696bcd809ab9 + FirebaseCore: 31f389c37ac1ea52454a53d3081f2d7019485a4a + FirebaseCoreDiagnostics: cad03be1904b975f845e632f2720c3337da27faf + FirebaseCrashlytics: c9eb562b2f6bd5ee5e880144fd5ef1bfe46c5dc5 + FirebaseInstallations: 1585729afc787877763208c2088ed84221161f77 + GoogleAppMeasurement: 6b6a08fd9c71f4dbc89e0e812acca81d797aa342 + GoogleDataTransport: 85fd18ff3019bb85d3f2c551d04c481dedf71fc9 + GoogleUtilities: eea970f4a389963963bffe8d8fabe43540678b9c + JOSESwift: 7ff178bb9173ff42c6e990929a9f2fa702a34f69 nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96 Nimble: 4f4a345c80b503b3ea13606a4f98405974ee4d0b - PromisesObjC: 3113f7f76903778cf4a0586bd1ab89329a0b7b97 + PromisesObjC: 68159ce6952d93e17b2dfe273b8c40907db5ba58 Quick: 6473349e43b9271a8d43839d9ba1c442ed1b7ac4 ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 Realm: c2ffe0667f9c98c9ba65f608ba85684d39826145 RealmSwift: 5a35c1c35c3e1925e6fcd092c881d5cc4a826bff - SaltedgeAuthenticatorSDK: b9260860f0e67fd169e8db903e92839584e1ab69 + SaltedgeAuthenticatorCore: bf9c91b81449fb7ccd23581ab84e1eeff5020647 + SaltedgeAuthenticatorSDK: 4fd566984678e8d936cae541aee82c89b44422e2 + SaltedgeAuthenticatorSDKv2: f9eb801575c0b90879c3f7cf667a7aabed743eef SDWebImage: c666b97e1fa9c64b4909816a903322018f0a9c84 TinyConstraints: 8dd91295e56797648c7bc335dd20e1d91ec4c192 Valet: 16d0537d70db79d9ba953b9060b5da4fb8004e51 -PODFILE CHECKSUM: 402996a4c66dc49d77bdf56b16a259e31c950d1f +PODFILE CHECKSUM: 7f15a08d33a1af46d3f787184ec65b0ed568eed4 COCOAPODS: 1.11.3 diff --git a/Example/Tests/Errors/CryptoErrorsSpec.swift b/Example/Tests/Errors/CryptoErrorsSpec.swift index d2523977..48323a9d 100644 --- a/Example/Tests/Errors/CryptoErrorsSpec.swift +++ b/Example/Tests/Errors/CryptoErrorsSpec.swift @@ -22,6 +22,7 @@ import Quick import Nimble +@testable import SEAuthenticatorCore @testable import SEAuthenticator class CryptoErrorsSpec: BaseSpec { diff --git a/Example/Tests/Extensions/DateExtensionsSpec.swift b/Example/Tests/Extensions/DateExtensionsSpec.swift index 4fc8640d..7c5d8b9a 100644 --- a/Example/Tests/Extensions/DateExtensionsSpec.swift +++ b/Example/Tests/Extensions/DateExtensionsSpec.swift @@ -38,5 +38,13 @@ class DateExtensionsSpec: BaseSpec { expect(date.utcSeconds).to(equal(1568285040)) } } + + describe("numberOfDaysFromNow") { + it("should return difference betwen dates from now") { + let twoDaysFromNow = Calendar.current.date(byAdding: .day, value: 2, to: Date())! + + expect(twoDaysFromNow.numberOfDaysFromNow).to(equal(2)) + } + } } } diff --git a/Example/Tests/Extensions/SEEncryptedDataExtensionsSpec.swift b/Example/Tests/Extensions/SEEncryptedDataExtensionsSpec.swift index aadf7859..5b027725 100644 --- a/Example/Tests/Extensions/SEEncryptedDataExtensionsSpec.swift +++ b/Example/Tests/Extensions/SEEncryptedDataExtensionsSpec.swift @@ -22,11 +22,13 @@ import Quick import Nimble +import SEAuthenticatorCore @testable import SEAuthenticator +@testable import SEAuthenticatorV2 class SEEncryptedDataExtensionsSpec: BaseSpec { override func spec() { - describe("decryptedAuthorizationData()") { + describe("decryptedAuthorizationData") { context("when given response is connect") { it("should return decrypted data from given response") { let connection = Connection() @@ -78,5 +80,68 @@ class SEEncryptedDataExtensionsSpec: BaseSpec { } } } + + describe("decryptedAuthorizationDataV2") { + context("when authorization status is not final") { + it("should return decrypted data from given response") { + let connection = SpecUtils.createConnection(id: "2", apiVersion: "2") + let authorizationId = "150" + + let authMessage: [String: Any] = [ + "title": "Authorization V2", + "authorization_code": "code", + "description": ["text": "Test valid authorization"], + "created_at": Date().iso8601string, + "expires_at": Date().addingTimeInterval(3.0 * 60.0).iso8601string + ] + + let encryptedData = try! SECryptoHelper.encrypt(authMessage.jsonString!, tag: SETagHelper.create(for: connection.guid)) + + let dict: [String: Any] = [ + "data": encryptedData.data, + "key": encryptedData.key, + "iv": encryptedData.iv, + "id": Int(authorizationId)!, + "connection_id": Int(connection.id)!, + "status": "pending" + ] + + let expectedData = SEAuthorizationDataV2( + authMessage, + id: authorizationId, + connectionId: connection.id, + status: .pending + ) + + let response = SpecDecodableModel.create(from: dict) + expect(expectedData).to(equal(response?.decryptedAuthorizationDataV2!)) + } + } + + context("when authorization status is final") { + it("should return SEAuthorizationDataV2 from given response") { + let connection = SpecUtils.createConnection(id: "2", apiVersion: "2") + let authorizationId = "150" + + let dict: [String: Any] = [ + "data": "", + "key": "", + "iv": "", + "id": Int(authorizationId)!, + "connection_id": Int(connection.id)!, + "status": "confirmed" + ] + + let expectedData = SEAuthorizationDataV2( + id: authorizationId, + connectionId: connection.id, + status: .confirmed + ) + + let response = SpecDecodableModel.create(from: dict) + expect(expectedData).to(equal(response?.decryptedAuthorizationDataV2!)) + } + } + } } } diff --git a/Example/Tests/Extensions/StackViewExtensionsSpec.swift b/Example/Tests/Extensions/StackViewExtensionsSpec.swift new file mode 100644 index 00000000..c12e7b50 --- /dev/null +++ b/Example/Tests/Extensions/StackViewExtensionsSpec.swift @@ -0,0 +1,76 @@ +// +// StackViewExtensionsSpec +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Quick +import Nimble +import UIKit + +class StackViewExtensionsSpec: BaseSpec { + override func spec() { + let stackView = UIStackView() + + describe("init()") { + it("should initialize an stackView with given properties") { + let axis = NSLayoutConstraint.Axis.vertical + let alignment = UIStackView.Alignment.fill + let spacing: CGFloat = 15.0 + let distribution = UIStackView.Distribution.equalSpacing + stackView.axis = axis + stackView.alignment = alignment + stackView.spacing = spacing + stackView.distribution = distribution + stackView.translatesAutoresizingMaskIntoConstraints = false + + let actualStackView = UIStackView(axis: axis, alignment: alignment, spacing: spacing, distribution: distribution) + + expect(stackView.axis).to(equal(actualStackView.axis)) + expect(stackView.alignment).to(equal(actualStackView.alignment)) + expect(stackView.spacing).to(equal(actualStackView.spacing)) + expect(stackView.distribution).to(equal(actualStackView.distribution)) + } + } + + describe("removeAllArrangedSubviews") { + it("should remove all arranged subviews of stackView") { + stackView.addArrangedSubviews(UIView(), UIView()) + + expect(stackView.arrangedSubviews.count).to(equal(2)) + + stackView.removeAllArrangedSubviews() + + expect(stackView.arrangedSubviews).to(beEmpty()) + } + } + + describe("addArrangedSubviews") { + it("should add subviews to stackView") { + let firstView = UIView() + let secondView = UIView() + let thirdView = UIView() + stackView.addArrangedSubviews(firstView, secondView, thirdView) + + expect(stackView.arrangedSubviews).to(equal([firstView, secondView, thirdView])) + } + } + } +} + diff --git a/Example/Tests/Extensions/StringExtensionsSpec.swift b/Example/Tests/Extensions/StringExtensionsSpec.swift new file mode 100644 index 00000000..0e6183cb --- /dev/null +++ b/Example/Tests/Extensions/StringExtensionsSpec.swift @@ -0,0 +1,36 @@ +// +// StringExtensionsSpec +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Quick +import Nimble + +class StringExtensionsSpec: BaseSpec { + override func spec() { + describe("capitalizingFirstLetter") { + it("should capitalize only first word from sentence") { + let testString = "hello world" + + expect(testString.capitalizingFirstLetter()).to(equal("Hello world")) + } + } + } +} diff --git a/Example/Tests/Helpers/ApiVersionExtractorSpec.swift b/Example/Tests/Helpers/ApiVersionExtractorSpec.swift new file mode 100644 index 00000000..61b9c3e1 --- /dev/null +++ b/Example/Tests/Helpers/ApiVersionExtractorSpec.swift @@ -0,0 +1,47 @@ +// +// ApiVersionExtractorSpec +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Quick +import Nimble +@testable import SEAuthenticatorCore + +class ApiVersionExtractorSpec: BaseSpec { + override func spec() { + describe("apiVersion") { + context("when url contains version") { + it("should return correct version from given url") { + let urlString = "https://sca.banksalt.com/api/authenticator/v2/configurations/1" + + expect(urlString.apiVerion).to(equal("2")) + } + } + + context("when url doesn't contain version") { + it("should return default value 1") { + let urlString = "https://sca.banksalt.com/api/authenticator/configurations/1" + + expect(urlString.apiVerion).to(equal("1")) + } + } + } + } +} diff --git a/Example/Tests/Helpers/AuthorizationStatusSpec.swift b/Example/Tests/Helpers/AuthorizationStatusSpec.swift new file mode 100644 index 00000000..8ddb8e21 --- /dev/null +++ b/Example/Tests/Helpers/AuthorizationStatusSpec.swift @@ -0,0 +1,169 @@ +// +// AuthorizationStatusSpec +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Quick +import Nimble +@testable import SEAuthenticatorCore + +final class AuthorizationStatusSpec: BaseSpec { + override func spec() { + var authorizationStatus: AuthorizationStatus! + + describe("isClosed") { + it("should return true if authorization status is closed") { + authorizationStatus = .closed + + expect(authorizationStatus.isClosed).to(equal(true)) + } + + it("should return false if authorization status is closed") { + authorizationStatus = .pending + + expect(authorizationStatus.isClosed).to(equal(false)) + + authorizationStatus = .processing + + expect(authorizationStatus.isClosed).to(equal(false)) + + authorizationStatus = .confirmed + + expect(authorizationStatus.isClosed).to(equal(false)) + + authorizationStatus = .denied + + expect(authorizationStatus.isClosed).to(equal(false)) + + authorizationStatus = .error + + expect(authorizationStatus.isClosed).to(equal(false)) + + authorizationStatus = .timeOut + + expect(authorizationStatus.isClosed).to(equal(false)) + + authorizationStatus = .unavailable + + expect(authorizationStatus.isClosed).to(equal(false)) + + authorizationStatus = .confirmProcessing + + expect(authorizationStatus.isClosed).to(equal(false)) + + authorizationStatus = .denyProcessing + + expect(authorizationStatus.isClosed).to(equal(false)) + } + } + + describe("isProcessing") { + it("should return true if authorization status is processing") { + authorizationStatus = .confirmProcessing + + expect(authorizationStatus.isProcessing).to(equal(true)) + + authorizationStatus = .denyProcessing + + expect(authorizationStatus.isProcessing).to(equal(true)) + } + + it("should return false if authorization status is processing") { + authorizationStatus = .pending + + expect(authorizationStatus.isProcessing).to(equal(false)) + + authorizationStatus = .processing + + expect(authorizationStatus.isProcessing).to(equal(false)) + + authorizationStatus = .confirmed + + expect(authorizationStatus.isProcessing).to(equal(false)) + + authorizationStatus = .denied + + expect(authorizationStatus.isProcessing).to(equal(false)) + + authorizationStatus = .error + + expect(authorizationStatus.isProcessing).to(equal(false)) + + authorizationStatus = .timeOut + + expect(authorizationStatus.isProcessing).to(equal(false)) + + authorizationStatus = .unavailable + + expect(authorizationStatus.isProcessing).to(equal(false)) + + authorizationStatus = .closed + + expect(authorizationStatus.isProcessing).to(equal(false)) + } + } + + describe("isFinal") { + it("should return true if authorization status is final") { + authorizationStatus = .confirmed + + expect(authorizationStatus.isFinal).to(equal(true)) + + authorizationStatus = .denied + + expect(authorizationStatus.isFinal).to(equal(true)) + + authorizationStatus = .error + + expect(authorizationStatus.isFinal).to(equal(true)) + + authorizationStatus = .timeOut + + expect(authorizationStatus.isFinal).to(equal(true)) + + authorizationStatus = .unavailable + + expect(authorizationStatus.isFinal).to(equal(true)) + } + + it("should return false if authorization status is final") { + authorizationStatus = .pending + + expect(authorizationStatus.isFinal).to(equal(false)) + + authorizationStatus = .processing + + expect(authorizationStatus.isFinal).to(equal(false)) + + authorizationStatus = .confirmProcessing + + expect(authorizationStatus.isFinal).to(equal(false)) + + authorizationStatus = .denyProcessing + + expect(authorizationStatus.isFinal).to(equal(false)) + + authorizationStatus = .closed + + expect(authorizationStatus.isFinal).to(equal(false)) + } + } + } +} diff --git a/Example/Tests/Helpers/DateUtilsSpec.swift b/Example/Tests/Helpers/DateUtilsSpec.swift index 12002b26..2ac07a39 100644 --- a/Example/Tests/Helpers/DateUtilsSpec.swift +++ b/Example/Tests/Helpers/DateUtilsSpec.swift @@ -22,7 +22,7 @@ import Quick import Nimble -@testable import SEAuthenticator +@testable import SEAuthenticatorCore class DateUtilsSpec: BaseSpec { override func spec() { diff --git a/Example/Tests/Helpers/SEConnectHelperSpec.swift b/Example/Tests/Helpers/SEConnectHelperSpec.swift index 9d80c011..86b054f1 100644 --- a/Example/Tests/Helpers/SEConnectHelperSpec.swift +++ b/Example/Tests/Helpers/SEConnectHelperSpec.swift @@ -22,7 +22,7 @@ import Quick import Nimble -@testable import SEAuthenticator +@testable import SEAuthenticatorCore class SEConnectHelperSpec: BaseSpec { override func spec() { @@ -58,6 +58,40 @@ class SEConnectHelperSpec: BaseSpec { } } + describe("isValidAction") { + context("when api version is 2") { + it("should return true if url containts api_version, action_id and provider_id") { + let url = URL(string: "authenticator://saltedge.com/action?api_version=2&action_id=1&provider_id=1&return_to=http://return.com")! + + expect(SEConnectHelper.isValidAction(deepLinkUrl: url)).to(beTrue()) + } + } + + context("when url is missing one of the requirement fields") { + it("should return false") { + let url = URL(string: "authenticator://saltedge.com/action?api_version=2&action_id=1&return_to=http://return.com")! + + expect(SEConnectHelper.isValidAction(deepLinkUrl: url)).to(beFalse()) + } + } + + context("if it's api v1 and contains action_uuid and connect_url") { + it("should return true") { + let url = URL(string: "authenticator://saltedge.com/action?action_uuid=123456&connect_url=https://connect.com")! + + expect(SEConnectHelper.isValidAction(deepLinkUrl: url)).to(beTrue()) + } + } + + context("if it's api v1 and url is missing required fields") { + it("should return false") { + let url = URL(string: "authenticator://saltedge.com/action?action_uuid=123456")! + + expect(SEConnectHelper.isValidAction(deepLinkUrl: url)).to(beFalse()) + } + } + } + describe("actionGuid") { it("should return action_uuid param value or nil") { let expectUrl = URL(string: "authenticator://saltedge.com/action?action_uuid=123456") @@ -122,5 +156,39 @@ class SEConnectHelperSpec: BaseSpec { expect(SEConnectHelper.connectQuery(from: expectUrl!)).to(equal("1234567890")) } } + + describe("shouldStartInstantActionFlow") { + context("when it's v2 and contains action_id and provider_id") { + it("should return true") { + let url = URL(string: "authenticator://saltedge.com/action?api_version=2&action_id=1&provider_id=1&return_to=http://return.com")! + + expect(SEConnectHelper.shouldStartInstantActionFlow(url: url)).to(beTrue()) + } + } + + context("when it's v2 and url is missing one of the required fields") { + it("should return false") { + let url = URL(string: "authenticator://saltedge.com/action?api_version=2&provider_id=1&return_to=http://return.com")! + + expect(SEConnectHelper.shouldStartInstantActionFlow(url: url)).to(beFalse()) + } + } + + context("when it's v1 and contains action_uuid and connect_url") { + it("should return true") { + let url = URL(string: "authenticator://saltedge.com/action?action_uuid=123456&connect_url=https://connect.com")! + + expect(SEConnectHelper.shouldStartInstantActionFlow(url: url)).to(beTrue()) + } + } + + context("when it's v1 and url is missing one of the required fields") { + it("should return false") { + let url = URL(string: "authenticator://saltedge.com/action?connect_url=https://connect.com")! + + expect(SEConnectHelper.shouldStartInstantActionFlow(url: url)).to(beFalse()) + } + } + } } } diff --git a/Example/Tests/Models/Collectors/ConnectionCollectorSpec.swift b/Example/Tests/Models/Collectors/ConnectionCollectorSpec.swift index 834868ed..2cba8b39 100644 --- a/Example/Tests/Models/Collectors/ConnectionCollectorSpec.swift +++ b/Example/Tests/Models/Collectors/ConnectionCollectorSpec.swift @@ -86,6 +86,21 @@ class ConnectionCollectorSpec: BaseSpec { } } + describe("activeConnections(by providerId)") { + it("should return array only of active connections filtered by connect url") { + let providerId = "1" + + let fifthConnection = Connection() + fifthConnection.id = "fifth" + fifthConnection.status = "active" + fifthConnection.providerId = providerId + + ConnectionRepository.save(fifthConnection) + + expect(Array(ConnectionsCollector.activeConnections(by: providerId))).to(equal([fifthConnection])) + } + } + describe("where:") { it("should properly serialize the arguments into the Object.filter call") { let whereString = "guid == '\(firstConnection.guid)'" diff --git a/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift b/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift index 90753268..492dac4a 100644 --- a/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift +++ b/Example/Tests/Models/Data Sources/AuthorizationsDataSourceSpec.swift @@ -23,12 +23,12 @@ import Quick import Nimble @testable import SEAuthenticator +@testable import SEAuthenticatorCore class AuthorizationsDataSourceSpec: BaseSpec { override func spec() { var firstModel, secondModel: AuthorizationDetailViewModel! - let mockLocationManager = MockLocationManager() - let dataSource = AuthorizationsDataSource(locationManagement: mockLocationManager) + let dataSource = AuthorizationsDataSource() var connection: Connection! beforeEach { @@ -51,8 +51,8 @@ class AuthorizationsDataSourceSpec: BaseSpec { let firstDecryptedData = SpecUtils.createAuthResponse(with: authMessage, id: connection.id, guid: connection.guid) let secondDecryptedData = SpecUtils.createAuthResponse(with: secondAuthMessage, id: connection.id, guid: connection.guid) - firstModel = AuthorizationDetailViewModel(firstDecryptedData, showLocationWarning: true) - secondModel = AuthorizationDetailViewModel(secondDecryptedData, showLocationWarning: false) + firstModel = AuthorizationDetailViewModel(firstDecryptedData, apiVersion: "1") + secondModel = AuthorizationDetailViewModel(secondDecryptedData, apiVersion: "1") _ = dataSource.update(with: [firstDecryptedData, secondDecryptedData]) } @@ -113,7 +113,7 @@ class AuthorizationsDataSourceSpec: BaseSpec { "created_at": Date().iso8601string, "expires_at": Date().addingTimeInterval(-3.0 * 60.0).iso8601string] let expiredDecryptedData = SpecUtils.createAuthResponse(with: expiredAuthMessage, id: connection.id, guid: connection.guid) - + let validAuthMessage = ["id": validAuthId, "connection_id": connection.id, "title": "Valid Authorization", @@ -121,24 +121,53 @@ class AuthorizationsDataSourceSpec: BaseSpec { "created_at": Date().iso8601string, "expires_at": Date().addingTimeInterval(3.0 * 60.0).iso8601string] let validDecryptedData = SpecUtils.createAuthResponse(with: validAuthMessage, id: connection.id, guid: connection.guid) - mockLocationManager.showLocationWarning = true _ = dataSource.update(with: [expiredDecryptedData, validDecryptedData]) expect(dataSource.rows).to(equal(1)) - expect(dataSource.viewModel(with: validAuthId)) - .to(equal(AuthorizationDetailViewModel(validDecryptedData, showLocationWarning: true))) - expect(dataSource.viewModel(with: validAuthId)!.showLocationWarning).to(beTrue()) - expect(dataSource.viewModel(with: expiredAuthId)).to(beNil()) + expect(dataSource.viewModel(with: validAuthId, apiVersion: "1")) + .to(equal(AuthorizationDetailViewModel(validDecryptedData,apiVersion: "1"))) - mockLocationManager.showLocationWarning = false + expect(dataSource.viewModel(with: expiredAuthId, apiVersion: "1")).to(beNil()) _ = dataSource.update(with: [validDecryptedData]) expect(dataSource.rows).to(equal(1)) - expect(dataSource.viewModel(with: validAuthId)) - .to(equal(AuthorizationDetailViewModel(validDecryptedData, showLocationWarning: false))) - expect(dataSource.viewModel(with: validAuthId)!.showLocationWarning).to(beFalse()) + expect(dataSource.viewModel(with: validAuthId, apiVersion: "1")) + .to(equal(AuthorizationDetailViewModel(validDecryptedData, apiVersion: "1"))) + } + } + + context("when new v2 authorizations were added") { + it("should return both v1 and v2 authorizations") { + // new authorization v1 + let validAuthMessage = ["id": "1111", + "connection_id": connection.id, + "title": "Valid Authorization", + "description": "Test valid authorization", + "created_at": Date().iso8601string, + "expires_at": Date().addingTimeInterval(3.0 * 60.0).iso8601string] + let validDecryptedDataV1 = SpecUtils.createAuthResponse(with: validAuthMessage, id: connection.id, guid: connection.guid) + + let connectionV2 = SpecUtils.createConnection(id: "999", apiVersion: "2") + let authorizationV2id = 30000 + + // new authorization v2 + let authMessage: [String: Any] = ["title": "Authorization V2", + "authorization_code": "code", + "description": ["text": "Test valid authorization"], + "created_at": Date().iso8601string, + "expires_at": Date().addingTimeInterval(3.0 * 60.0).iso8601string] + let validDecryptedDataV2 = SpecUtils.createAuthResponseV2( + with: authMessage, + authorizationId: authorizationV2id, + connectionId: Int(connectionV2.id)!, + guid: connectionV2.guid + ) + + _ = dataSource.update(with: [validDecryptedDataV1, validDecryptedDataV2]) + + expect(dataSource.rows).to(equal(2)) } } @@ -146,7 +175,7 @@ class AuthorizationsDataSourceSpec: BaseSpec { context("when one authorization expired and other was added") { it("should keep the expired one and add the new one") { expect(dataSource.rows).to(equal(2)) - + let authMessage = ["id": "909", "connection_id": connection.id, "title": "Expired Authorization", @@ -154,12 +183,12 @@ class AuthorizationsDataSourceSpec: BaseSpec { "created_at": Date().iso8601string, "expires_at": Date().addingTimeInterval(1.0).iso8601string] let decryptedData = SpecUtils.createAuthResponse(with: authMessage, id: connection.id, guid: connection.guid) - + _ = dataSource.update(with: [decryptedData]) sleep(1) - + expect(dataSource.rows).to(equal(1)) - + let newAuthMessage = ["id": "910", "connection_id": connection.id, "title": "Expired Authorization", @@ -167,12 +196,88 @@ class AuthorizationsDataSourceSpec: BaseSpec { "created_at": Date().iso8601string, "expires_at": Date().addingTimeInterval(3.0 * 60.0).iso8601string] let newDecryptedData = SpecUtils.createAuthResponse(with: newAuthMessage, id: connection.id, guid: connection.guid) - + _ = dataSource.update(with: [newDecryptedData]) - + expect(dataSource.rows).to(equal(2)) } } + + context("when recieved authorization with final state from v2") { + it("should set final state and leave only one") { + expect(dataSource.rows).to(equal(2)) + + let authMessage: [String: Any] = [ + "title": "Authorization V2", + "authorization_code": "code", + "description": ["text": "Test valid authorization"], + "created_at": Date().iso8601string, + "expires_at": Date().addingTimeInterval(3.0 * 60.0).iso8601string + ] + + let decryptedData = SpecUtils.createAuthResponseV2( + with: authMessage, + authorizationId: 909, + connectionId: Int(connection.id)!, + guid: connection.guid + ) + + _ = dataSource.update(with: [decryptedData]) + sleep(1) + + expect(dataSource.rows).to(equal(1)) + + let finalStatusAuthorization = SpecUtils.createNotEncryptedAuthResponseV2( + with: authMessage, + authorizationId: 909, + connectionId: Int(connection.id)!, + guid: connection.guid, + status: AuthorizationStatus.denied + ) + + _ = dataSource.update(with: [finalStatusAuthorization]) + + expect(dataSource.rows).to(equal(1)) + } + } + } + } + + describe("toAuthorizationViewModels with closed state") { + it("when recieved authorization with closed state we should skip it") { + expect(dataSource.rows).to(equal(2)) + + let authMessage: [String: Any] = [ + "title": "Authorization V2", + "authorization_code": "code", + "description": ["text": "Test valid authorization"], + "created_at": Date().iso8601string, + "expires_at": Date().addingTimeInterval(3.0 * 60.0).iso8601string + ] + + let decryptedData = SpecUtils.createAuthResponseV2( + with: authMessage, + authorizationId: 909, + connectionId: Int(connection.id)!, + guid: connection.guid, + status: "closed" + ) + + _ = dataSource.update(with: [decryptedData]) + + expect(dataSource.rows).to(equal(1)) + + let closedStatusAuthorization = SpecUtils.createNotEncryptedAuthResponseV2( + with: authMessage, + authorizationId: 909, + connectionId: Int(connection.id)!, + guid: connection.guid, + status: AuthorizationStatus.closed + ) + + _ = dataSource.update(with: [closedStatusAuthorization]) + + expect(dataSource.rows).to(equal(0)) } } @@ -220,7 +325,7 @@ class AuthorizationsDataSourceSpec: BaseSpec { describe("viewModel(by:)") { context("when one of existed viewModels has connectionId and authorizationId equal for given params") { - it("should return existed viewModel") { + it("should return existed viewModel") { expect(dataSource.viewModel(by: "12345", authorizationId: "00000")).to(equal(firstModel)) } } @@ -249,16 +354,9 @@ class AuthorizationsDataSourceSpec: BaseSpec { "expires_at": Date().addingTimeInterval(5.0 * 60.0).iso8601string] let decryptedData = SEAuthorizationData(secondAuthMessage)! - expect(dataSource.index(of: AuthorizationDetailViewModel(decryptedData, showLocationWarning: true)!)).to(beNil()) + expect(dataSource.index(of: AuthorizationDetailViewModel(decryptedData, apiVersion: "1")!)).to(beNil()) } } } - - describe("item(for)") { - it("should return view model for given index") { - expect(dataSource.viewModel(at: 0)).to(equal(firstModel)) - expect(dataSource.viewModel(at: 1)).to(equal(secondModel)) - } - } } } diff --git a/Example/Tests/Models/Database/ConnectionSpec.swift b/Example/Tests/Models/Database/ConnectionSpec.swift index bd5857d0..1574928b 100644 --- a/Example/Tests/Models/Database/ConnectionSpec.swift +++ b/Example/Tests/Models/Database/ConnectionSpec.swift @@ -60,5 +60,25 @@ class ConnectionSpec: BaseSpec { expect(connection.isManaged).to(beTrue()) } } + + describe("isApiV2") { + it("should return true if apiVersion value is 2") { + let connection = Connection() + + expect(connection.isApiV2).to(beFalse()) + + connection.apiVersion = "2" + + expect(connection.isApiV2).to(beTrue()) + } + } + + describe("providerPublicKeyTag") { + it("should return connection's provider public key tag") { + let connection = Connection() + + expect("\(connection.guid)_provider_public_key").to(equal(connection.providerPublicKeyTag)) + } + } } } diff --git a/Example/Tests/Models/Structures/ConnectionDataSpec.swift b/Example/Tests/Models/Structures/ConnectionDataSpec.swift index 58d94c5f..0160dffa 100644 --- a/Example/Tests/Models/Structures/ConnectionDataSpec.swift +++ b/Example/Tests/Models/Structures/ConnectionDataSpec.swift @@ -22,6 +22,7 @@ import Quick import Nimble +import SEAuthenticatorCore @testable import SEAuthenticator class ConnectionDataSpec: BaseSpec { diff --git a/Example/Tests/Networking/Responses/ConfirmAuthorizationResponseSpec.swift b/Example/Tests/Networking/Responses/ConfirmAuthorizationResponseSpec.swift index 7e8a7c98..c784fb37 100644 --- a/Example/Tests/Networking/Responses/ConfirmAuthorizationResponseSpec.swift +++ b/Example/Tests/Networking/Responses/ConfirmAuthorizationResponseSpec.swift @@ -30,7 +30,7 @@ class ConfirmAuthorizationResponseSpec: BaseSpec { context("when the value is a proper dictionary containing the necessary data") { it("should create correct response") { let fixture = DataFixtures.validConfirmAuthorizationData - let response = SEConfirmAuthorizationResponse(fixture) + let response = SpecDecodableModel.create(from: fixture) expect(response).toNot(beNil()) expect(response?.success).to(beTrue()) @@ -41,7 +41,7 @@ class ConfirmAuthorizationResponseSpec: BaseSpec { context("when the value is a malformed dictionary or is missing data") { it("should return nil and fail to initialize the object") { let fixture = DataFixtures.invalidConfirmAuthorizationData - let response = SEConfirmAuthorizationResponse(fixture) + let response = SpecDecodableModel.create(from: fixture) expect(response).to(beNil()) } diff --git a/Example/Tests/Networking/Responses/CreateConnectionResponseSpec.swift b/Example/Tests/Networking/Responses/CreateConnectionResponseSpec.swift index 1f9d7b89..857f8d32 100644 --- a/Example/Tests/Networking/Responses/CreateConnectionResponseSpec.swift +++ b/Example/Tests/Networking/Responses/CreateConnectionResponseSpec.swift @@ -30,19 +30,19 @@ class CreateConnectionResponseSpec: BaseSpec { context("when the value is a proper dictionary containing the necessary data") { it("should create correct response") { let fixture = DataFixtures.validCreateConnectionData - let expectedResponse = SECreateConnectionResponse(fixture) + let response = SpecDecodableModel.create(from: fixture) - expect(expectedResponse).toNot(beNil()) - expect(expectedResponse?.connectUrl).to(equal("connect.com")) - expect(expectedResponse?.id).to(equal("123456789")) + expect(response).toNot(beNil()) + expect(response?.connectUrl).to(equal("connect.com")) + expect(response?.id).to(equal("123456789")) } } context("when the value is a malformed dictionary or is missing data") { it("should return nil and fail to initialize the object") { let fixture = DataFixtures.invalidCreateConnectionData - let response = SECreateConnectionResponse(fixture) - + let response = SpecDecodableModel.create(from: fixture) + expect(response).to(beNil()) } } diff --git a/Example/Tests/Networking/Responses/ProviderResponseSpec.swift b/Example/Tests/Networking/Responses/ProviderResponseSpec.swift index 2b4607f7..6906179f 100644 --- a/Example/Tests/Networking/Responses/ProviderResponseSpec.swift +++ b/Example/Tests/Networking/Responses/ProviderResponseSpec.swift @@ -30,21 +30,21 @@ class ProviderResponseSpec: BaseSpec { context("when the value is a proper dictionary containing the necessary data") { it("should create correct response") { let fixture = DataFixtures.validProviderResponse - let expectedResponse = SEProviderResponse(fixture) + let response = SpecDecodableModel.create(from: fixture) - expect(expectedResponse).toNot(beNil()) - expect(expectedResponse?.code).to(equal("demobank")) - expect(expectedResponse?.connectUrl.absoluteString).to(equal("getConnectUrl.com")) - expect(expectedResponse?.name).to(equal("Demobank")) - expect(expectedResponse?.logoUrl?.absoluteString).to(equal("https://upload.wikimedia.org/wikipedia/commons/6/6f/HP_logo_630x630.png")) - expect(expectedResponse?.version).to(equal("1")) + expect(response).toNot(beNil()) + expect(response?.code).to(equal("demobank")) + expect(response?.baseUrl.absoluteString).to(equal("getConnectUrl.com")) + expect(response?.name).to(equal("Demobank")) + expect(response?.logoUrl?.absoluteString).to(equal("https://upload.wikimedia.org/wikipedia/commons/6/6f/HP_logo_630x630.png")) + expect(response?.version).to(equal("1")) } } context("when the value is a malformed dictionary or is missing data") { it("should return nil and fail to initialize the object") { let fixture = DataFixtures.invalidProviderResponse - let response = SEProviderResponse(fixture) + let response = SpecDecodableModel.create(from: fixture) expect(response).to(beNil()) } diff --git a/Example/Tests/Networking/Responses/RevokeConnectionResponseSpec.swift b/Example/Tests/Networking/Responses/RevokeConnectionResponseSpec.swift index 9f575386..b1c447fd 100644 --- a/Example/Tests/Networking/Responses/RevokeConnectionResponseSpec.swift +++ b/Example/Tests/Networking/Responses/RevokeConnectionResponseSpec.swift @@ -30,7 +30,7 @@ class RevokeConnectionResponseSpec: BaseSpec { context("when the value is a proper dictionary containing the necessary data") { it("should create correct response") { let fixture = DataFixtures.validRevokeConnectionData - let response = SERevokeConnectionResponse(fixture) + let response = SpecDecodableModel.create(from: fixture) expect(response).toNot(beNil()) expect(response?.success).to(beTrue()) @@ -40,8 +40,8 @@ class RevokeConnectionResponseSpec: BaseSpec { context("when the value is a malformed dictionary or is missing data") { it("should return nil and fail to initialize the object") { let fixture = DataFixtures.invalidRevokeConnectionData - let response = SERevokeConnectionResponse(fixture) - + let response = SpecDecodableModel.create(from: fixture) + expect(response).to(beNil()) } } diff --git a/Example/Tests/Networking/Responses/SubmitActionResponseSpec.swift b/Example/Tests/Networking/Responses/SubmitActionResponseSpec.swift index 5f3b1887..4978da83 100644 --- a/Example/Tests/Networking/Responses/SubmitActionResponseSpec.swift +++ b/Example/Tests/Networking/Responses/SubmitActionResponseSpec.swift @@ -30,7 +30,7 @@ class SubmitActionResponseSpec: BaseSpec { context("when the value is a proper dictionary containing the necessary data") { it("should create correct response") { let fixture = DataFixtures.validSubmitActionData - let response = SESubmitActionResponse(fixture) + let response = SpecDecodableModel.create(from: fixture) expect(response).toNot(beNil()) expect(response?.success).to(beTrue()) @@ -42,7 +42,7 @@ class SubmitActionResponseSpec: BaseSpec { context("when the value is a malformed dictionary or is missing data") { it("should return nil and fail to initialize the object") { let fixture = DataFixtures.invalidSubmitActionData - let response = SESubmitActionResponse(fixture) + let response = SpecDecodableModel.create(from: fixture) expect(response).to(beNil()) } diff --git a/Example/Tests/Networking/Routers/ActionRouterSpec.swift b/Example/Tests/Networking/Routers/v1/ActionRouterSpec.swift similarity index 92% rename from Example/Tests/Networking/Routers/ActionRouterSpec.swift rename to Example/Tests/Networking/Routers/v1/ActionRouterSpec.swift index 6ac34ad2..4cbf859d 100644 --- a/Example/Tests/Networking/Routers/ActionRouterSpec.swift +++ b/Example/Tests/Networking/Routers/v1/ActionRouterSpec.swift @@ -22,6 +22,7 @@ import Quick import Nimble +import SEAuthenticatorCore @testable import SEAuthenticator class ActionRouterSpec: BaseSpec { @@ -66,8 +67,10 @@ class ActionRouterSpec: BaseSpec { let request = SEActionRouter.submit(expectedActionData).asURLRequest() expect(request).to(equal(expectedRequest)) + + expect(request.allHTTPHeaderFields!["User-Agent"]) + .to(equal("TestHost-iOS / 1.0(1); (simulator/sandbox; iOS \(UIDevice.current.systemVersion))")) } } } } - diff --git a/Example/Tests/Networking/Routers/AuthorizationRouterSpec.swift b/Example/Tests/Networking/Routers/v1/AuthorizationRouterSpec.swift similarity index 99% rename from Example/Tests/Networking/Routers/AuthorizationRouterSpec.swift rename to Example/Tests/Networking/Routers/v1/AuthorizationRouterSpec.swift index 18cd856a..a440f70a 100644 --- a/Example/Tests/Networking/Routers/AuthorizationRouterSpec.swift +++ b/Example/Tests/Networking/Routers/v1/AuthorizationRouterSpec.swift @@ -22,6 +22,7 @@ import Quick import Nimble +import SEAuthenticatorCore @testable import SEAuthenticator class AuthorizationRouterSpec: BaseSpec { diff --git a/Example/Tests/Networking/Routers/ConnectionRouterSpec.swift b/Example/Tests/Networking/Routers/v1/ConnectionRouterSpec.swift similarity index 96% rename from Example/Tests/Networking/Routers/ConnectionRouterSpec.swift rename to Example/Tests/Networking/Routers/v1/ConnectionRouterSpec.swift index bb2215e5..77ee20f3 100644 --- a/Example/Tests/Networking/Routers/ConnectionRouterSpec.swift +++ b/Example/Tests/Networking/Routers/v1/ConnectionRouterSpec.swift @@ -22,6 +22,7 @@ import Quick import Nimble +import SEAuthenticatorCore @testable import SEAuthenticator class ConnectionRouterSpec: BaseSpec { @@ -35,7 +36,7 @@ class ConnectionRouterSpec: BaseSpec { let data = SECreateConnectionRequestData(code: "code", tag: "guid")! let expectedRequest = URLRequestBuilder.buildUrlRequest( - with: baseUrl, + with: baseUrl.appendingPathComponent("/api/authenticator/v1/connections"), method: HTTPMethod.post.rawValue, headers: Headers.requestHeaders(with: "en"), params: RequestParametersBuilder.parameters( diff --git a/Example/Tests/Networking/Routers/ProviderRouterSpec.swift b/Example/Tests/Networking/Routers/v1/ProviderRouterSpec.swift similarity index 98% rename from Example/Tests/Networking/Routers/ProviderRouterSpec.swift rename to Example/Tests/Networking/Routers/v1/ProviderRouterSpec.swift index 621b8736..732c5175 100644 --- a/Example/Tests/Networking/Routers/ProviderRouterSpec.swift +++ b/Example/Tests/Networking/Routers/v1/ProviderRouterSpec.swift @@ -22,6 +22,7 @@ import Quick import Nimble +import SEAuthenticatorCore @testable import SEAuthenticator class ProviderRouterSpec: BaseSpec { diff --git a/SaltedgeAuthenticatorSDK/Classes/API/SerializableResponse.swift b/Example/Tests/Spec Helpers/MockConnectable.swift similarity index 83% rename from SaltedgeAuthenticatorSDK/Classes/API/SerializableResponse.swift rename to Example/Tests/Spec Helpers/MockConnectable.swift index 10bf556d..0644221b 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/SerializableResponse.swift +++ b/Example/Tests/Spec Helpers/MockConnectable.swift @@ -1,8 +1,8 @@ // -// SerializableResponse.swift +// MockConnectable // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -22,6 +22,9 @@ import Foundation -public protocol SerializableResponse { - init?(_ value: Any) +class MockConnectable: Connectable { + var enabled = true + var isConnected: Bool { + return enabled + } } diff --git a/Example/Tests/Spec Helpers/MockLocationManager.swift b/Example/Tests/Spec Helpers/MockLocationManager.swift index 09c796be..f738a5f0 100644 --- a/Example/Tests/Spec Helpers/MockLocationManager.swift +++ b/Example/Tests/Spec Helpers/MockLocationManager.swift @@ -29,7 +29,7 @@ class MockLocationManager: LocationManagement { var geolocationSharingIsEnabled: Bool = false - func showLocationWarning(connection: Connection?) -> Bool { + func shouldShowLocationWarning(connection: Connection?) -> Bool { return showLocationWarning } } diff --git a/Example/Tests/Spec Helpers/SpecCryptoHelper.swift b/Example/Tests/Spec Helpers/SpecCryptoHelper.swift index 69663e2b..8cd16d6b 100644 --- a/Example/Tests/Spec Helpers/SpecCryptoHelper.swift +++ b/Example/Tests/Spec Helpers/SpecCryptoHelper.swift @@ -21,6 +21,7 @@ // import Foundation +import SEAuthenticatorCore @testable import SEAuthenticator struct SpecCryptoHelper { diff --git a/Example/Tests/Spec Helpers/SpecUtils.swift b/Example/Tests/Spec Helpers/SpecUtils.swift index 3d6cc8cd..05d0f197 100644 --- a/Example/Tests/Spec Helpers/SpecUtils.swift +++ b/Example/Tests/Spec Helpers/SpecUtils.swift @@ -22,18 +22,31 @@ import Foundation import SEAuthenticator +import SEAuthenticatorV2 +import SEAuthenticatorCore struct SpecUtils { - static func createConnection(id: ID) -> Connection { + static func createConnection(id: ID, apiVersion: ApiVersion = "1", geolocationRequired: Bool = true) -> Connection { let connection = Connection() connection.id = id connection.baseUrlString = "url.com" + connection.apiVersion = apiVersion + connection.geolocationRequired.value = geolocationRequired ConnectionRepository.save(connection) _ = SECryptoHelper.createKeyPair(with: SETagHelper.create(for: connection.guid)) return connection } + static func privateKey(for tag: String) -> SecKey? { + do { + return try SECryptoHelper.privateKey(for: tag) + } catch { + print(error.localizedDescription) + } + return nil + } + static func createAuthResponse(with authMessage: [String: Any], id: ID, guid: GUID) -> SEAuthorizationData { let encryptedData = try! SECryptoHelper.encrypt(authMessage.jsonString!, tag: SETagHelper.create(for: guid)) @@ -47,4 +60,82 @@ struct SpecUtils { return SEEncryptedData(dict)!.decryptedAuthorizationData! } + + static func createAuthResponseV2( + with authMessage: [String: Any], + authorizationId: Int, + connectionId: Int, + guid: GUID, + status: String = "pending" + ) -> SEAuthorizationDataV2 { + let encryptedData = try! SECryptoHelper.encrypt(authMessage.jsonString!, tag: SETagHelper.create(for: guid)) + + let dict: [String: Any] = [ + "data": encryptedData.data, + "key": encryptedData.key, + "iv": encryptedData.iv, + "id": authorizationId, + "connection_id": connectionId, + "status": status + ] + + let response = SpecDecodableModel.create(from: dict)! + return response.decryptedAuthorizationDataV2! + } + + static func createNotEncryptedAuthResponseV2( + with authMessage: [String: Any], + authorizationId: Int, + connectionId: Int, + guid: GUID, + status: AuthorizationStatus + ) -> SEAuthorizationDataV2 { + return SEAuthorizationDataV2(id: "\(authorizationId)", connectionId: "\(connectionId)", status: status) + } + + + public static var publicKeyPem: String { + "-----BEGIN PUBLIC KEY-----\n" + + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAppVU/nZZVewUCRVLz51X\n" + + "iKcliziIOb5/ReqHH82ikgC517/7Qo/cBFK+/iOC+yDgULkJE3SMhG85JoCqeX7j\n" + + "YzeILe5LLgqAxLCOjQFnkQDaHwP2WShU8WQifZ58UY5Th2GCKScFrsLxPr8HLWJH\n" + + "cPC6qicuOmgvyT64SvWFh8l5nHWcx/RA7e5Z4eCRntqyVDv622/vYybNInFMvqB+\n" + + "oEGOhEyh/qCYmIumEH3QH91eqCd05/Z9PtugH08TqRPDL6s5GunfTsBHYhJdxDTc\n" + + "qh0etk+TnUqYON7jOXDAN7L8y5VI/UELVONBJy8MzcyER1pyPhrnCDMaKX6+LcpB\n" + + "owIDAQAB\n" + + "-----END PUBLIC KEY-----\n" + } +} + +public struct SpecDecodableModel { + public static func create(from fixture: [String: Any]) -> T? { + let decoder = JSONDecoder() + decoder.dateDecodingStrategyFormatters = [DateUtils.dateFormatter, DateUtils.ymdDateFormatter] + let fixtureData = Data(fixture.jsonString!.utf8) + return try? decoder.decode(T.self, from: fixtureData) + } +} + +private extension JSONDecoder { + var dateDecodingStrategyFormatters: [DateFormatter]? { + get { + return nil + } + set { + guard let formatters = newValue else { return } + + self.dateDecodingStrategy = .custom { decoder in + let container = try decoder.singleValueContainer() + let dateString = try container.decode(String.self) + + for formatter in formatters { + if let date = formatter.date(from: dateString) { + return date + } + } + + throw DecodingError.dataCorruptedError(in: container, debugDescription: "Cannot decode date string \(dateString)") + } + } + } } diff --git a/Example/Tests/Spec Helpers/URLRequestBuilder.swift b/Example/Tests/Spec Helpers/URLRequestBuilder.swift index aab144fa..6e15eaf4 100644 --- a/Example/Tests/Spec Helpers/URLRequestBuilder.swift +++ b/Example/Tests/Spec Helpers/URLRequestBuilder.swift @@ -21,17 +21,23 @@ // import Foundation -@testable import SEAuthenticator +import SEAuthenticatorCore struct URLRequestBuilder { - static func buildUrlRequest(with url: URL, - method: String, - headers: [String: String]? = nil, - params: [String: Any]? = nil, - encoding: Encoding = .json) -> URLRequest { + static func buildUrlRequest( + with url: URL, + method: String, + headers: [String: String]? = nil, + params: [String: Any]? = nil, + encoding: Encoding = .json + ) -> URLRequest { var request = URLRequest(url: url) request.httpMethod = method request.allHTTPHeaderFields = headers + request.setValue( + "TestHost-iOS / 1.0(1); (simulator/sandbox; iOS \(UIDevice.current.systemVersion))", + forHTTPHeaderField: "User-Agent" + ) if encoding == .url { var components = URLComponents(url: url, resolvingAgainstBaseURL: true) diff --git a/Example/Tests/ViewModels/AuthorizationsViewModelSpec.swift b/Example/Tests/ViewModels/AuthorizationsViewModelSpec.swift index ccf0a43e..26a2417e 100644 --- a/Example/Tests/ViewModels/AuthorizationsViewModelSpec.swift +++ b/Example/Tests/ViewModels/AuthorizationsViewModelSpec.swift @@ -42,7 +42,7 @@ final class AuthorizationsViewModelSpec: BaseSpec { context("when there is no connections") { it("should return correct data") { let viewModel = AuthorizationsViewModel() - let dataSource = AuthorizationsDataSource(locationManagement: mockLocationManager) + let dataSource = AuthorizationsDataSource() viewModel.dataSource = dataSource expect(Array(ConnectionsCollector.allConnections)).to(beEmpty()) @@ -61,7 +61,7 @@ final class AuthorizationsViewModelSpec: BaseSpec { context("when there is at least one connection") { it("should retun correct data") { let viewModel = AuthorizationsViewModel() - let dataSource = AuthorizationsDataSource(locationManagement: mockLocationManager) + let dataSource = AuthorizationsDataSource() viewModel.dataSource = dataSource let expectedEmptyViewData = EmptyViewData( @@ -85,7 +85,7 @@ final class AuthorizationsViewModelSpec: BaseSpec { describe("confirmAuthorization") { it("should set state to .processing, then to completion state") { let viewModel = AuthorizationsViewModel() - let dataSource = AuthorizationsDataSource(locationManagement: mockLocationManager) + let dataSource = AuthorizationsDataSource() viewModel.dataSource = dataSource let connection = SpecUtils.createConnection(id: "123") @@ -99,21 +99,52 @@ final class AuthorizationsViewModelSpec: BaseSpec { let decryptedData = SpecUtils.createAuthResponse(with: authMessage, id: connection.id, guid: connection.guid) + mockLocationManager.geolocationSharingIsEnabled = true _ = dataSource.update(with: [decryptedData]) - let detailViewModel = dataSource.viewModel(with: "00000") + let detailViewModel = dataSource.viewModel(with: "00000", apiVersion: "1") - viewModel.confirmAuthorization(by: "00000") + viewModel.confirmAuthorization(by: "00000", apiVersion: "1") expect(detailViewModel?.state.value).to(equal(AuthorizationStateView.AuthorizationState.processing)) - expect(detailViewModel?.state.value).toEventually(equal(AuthorizationStateView.AuthorizationState.undefined)) + expect(detailViewModel?.state.value).toEventually(equal(AuthorizationStateView.AuthorizationState.error)) + } + + context("when location services are off and conection requires geolocation") { + it("should set state to .requestLocationWarning") { + let viewModel = AuthorizationsViewModel() + let dataSource = AuthorizationsDataSource() + viewModel.dataSource = dataSource + + let connection = SpecUtils.createConnection(id: "123") + + try? RealmManager.performRealmWriteTransaction { + connection.status = "active" + } + + let authMessage = ["id": "00000", + "connection_id": connection.id, + "title": "Authorization", + "description": "Test authorization", + "created_at": Date().iso8601string, + "expires_at": Date().addingTimeInterval(5.0 * 60.0).iso8601string] + + let decryptedData = SpecUtils.createAuthResponse(with: authMessage, id: connection.id, guid: connection.guid) + + mockLocationManager.geolocationSharingIsEnabled = false + _ = dataSource.update(with: [decryptedData]) + + viewModel.confirmAuthorization(by: "00000", apiVersion: "1") + + expect(viewModel.state.value).to(equal(AuthorizationsViewModelState.requestLocationWarning)) + } } } describe("denyAuthorization") { it("should set state to .processing, then to completion state") { let viewModel = AuthorizationsViewModel() - let dataSource = AuthorizationsDataSource(locationManagement: mockLocationManager) + let dataSource = AuthorizationsDataSource() viewModel.dataSource = dataSource let connection = SpecUtils.createConnection(id: "123") @@ -127,14 +158,45 @@ final class AuthorizationsViewModelSpec: BaseSpec { let decryptedData = SpecUtils.createAuthResponse(with: authMessage, id: connection.id, guid: connection.guid) + mockLocationManager.geolocationSharingIsEnabled = true _ = dataSource.update(with: [decryptedData]) - let detailViewModel = dataSource.viewModel(with: "00000") + let detailViewModel = dataSource.viewModel(with: "00000", apiVersion: "1") - viewModel.denyAuthorization(by: "00000") + viewModel.denyAuthorization(by: "00000", apiVersion: "1") expect(detailViewModel?.state.value).to(equal(AuthorizationStateView.AuthorizationState.processing)) - expect(detailViewModel?.state.value).toEventually(equal(AuthorizationStateView.AuthorizationState.undefined)) + expect(detailViewModel?.state.value).toEventually(equal(AuthorizationStateView.AuthorizationState.error)) + } + + context("when location services are off and conection requires geolocation") { + it("should set state to .requestLocationWarning") { + let viewModel = AuthorizationsViewModel() + let dataSource = AuthorizationsDataSource() + viewModel.dataSource = dataSource + + let connection = SpecUtils.createConnection(id: "123") + + try? RealmManager.performRealmWriteTransaction { + connection.status = "active" + } + + let authMessage = ["id": "00000", + "connection_id": connection.id, + "title": "Authorization", + "description": "Test authorization", + "created_at": Date().iso8601string, + "expires_at": Date().addingTimeInterval(5.0 * 60.0).iso8601string] + + let decryptedData = SpecUtils.createAuthResponse(with: authMessage, id: connection.id, guid: connection.guid) + + mockLocationManager.geolocationSharingIsEnabled = false + _ = dataSource.update(with: [decryptedData]) + + viewModel.confirmAuthorization(by: "00000", apiVersion: "1") + + expect(viewModel.state.value).to(equal(AuthorizationsViewModelState.requestLocationWarning)) + } } } } diff --git a/Example/Tests/ViewModels/ConnectViewModelSpec.swift b/Example/Tests/ViewModels/ConnectViewModelSpec.swift new file mode 100644 index 00000000..3810b9d0 --- /dev/null +++ b/Example/Tests/ViewModels/ConnectViewModelSpec.swift @@ -0,0 +1,74 @@ +// +// ConnectViewModelSpec +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Quick +import Nimble + +private final class MockСonnectDelegate: ConnectViewModelEventsDelegate { + var showNoInternetConnectionAlertCall: Bool = false + + func showNoInternetConnectionAlert() { + showNoInternetConnectionAlertCall = true + } +} + +final class ConnectViewModelSpec: BaseSpec { + private var viewModel: ConnectViewModel! + private var networkSpy: MockConnectable! + + override func spec() { + beforeEach { + self.networkSpy = MockConnectable() + self.viewModel = ConnectViewModel(reachabilityManager: self.networkSpy) + } + afterEach { + self.viewModel = nil + self.networkSpy = nil + } + + describe("checkInternetConnection") { + context("check internet connection when internet is off") { + it("should show internet connection alert") { + let mockDelegate = MockСonnectDelegate() + self.viewModel.delegate = mockDelegate + self.networkSpy.enabled = false + + self.viewModel.checkInternetConnection() + + expect(mockDelegate.showNoInternetConnectionAlertCall).to(beTrue()) + } + } + + context("check internet connection when internet is on") { + it("should show internet connection alert") { + let mockDelegate = MockСonnectDelegate() + self.viewModel.delegate = mockDelegate + self.networkSpy.enabled = true + + self.viewModel.checkInternetConnection() + + expect(mockDelegate.showNoInternetConnectionAlertCall).to(beFalse()) + } + } + } + } +} diff --git a/Example/Tests/ViewModels/ConnectionsViewModelSpec.swift b/Example/Tests/ViewModels/ConnectionsViewModelSpec.swift new file mode 100644 index 00000000..c3657915 --- /dev/null +++ b/Example/Tests/ViewModels/ConnectionsViewModelSpec.swift @@ -0,0 +1,229 @@ +// +// ConnectionsViewModelSpec +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Quick +import Nimble +import SEAuthenticator +@testable import SEAuthenticatorCore + +private final class MockConnectionsDelegate: ConnectionsEventsDelegate { + var showEditConnectionAlertCall: Bool = false + var showSupportCall: Bool = false + var deleteConnectionCall: Bool = false + var reconnectCall: Bool = false + var consentsPressedCall: Bool = false + var updateViewsCall: Bool = false + var addPressedCall: Bool = false + var presentErrorCall: Bool = false + var showNoInternetConnectionAlertCall: Bool = false + var showDeleteConfirmationAlertCall: Bool = false + + func showEditConnectionAlert(placeholder: String, completion: @escaping (String) -> ()) { + showEditConnectionAlertCall = true + } + + func showSupport(email: String) { + showSupportCall = true + } + + func deleteConnection(completion: @escaping () -> ()) { + deleteConnectionCall = true + } + + func reconnect(by id: String) { + reconnectCall = true + } + + func consentsPressed(connectionId: String, consents: [SEConsentData]) { + consentsPressedCall = true + } + + func updateViews() { + updateViewsCall = true + } + + func addPressed() { + addPressedCall = true + } + + func presentError(_ error: String) { + presentErrorCall = true + } + + func showNoInternetConnectionAlert(completion: @escaping () -> Void) { + showNoInternetConnectionAlertCall = true + } + + func showDeleteConfirmationAlert(completion: @escaping () -> Void) { + showDeleteConfirmationAlertCall = true + } +} + +final class ConnectionsViewModelSpec: BaseSpec { + override func spec() { + var viewModel: ConnectionsViewModel! + var networkSpy: MockConnectable! + + beforeEach { + networkSpy = MockConnectable() + viewModel = ConnectionsViewModel(reachabilityManager: networkSpy) + ConnectionRepository.deleteAllConnections() + } + afterEach { + viewModel = nil + networkSpy = nil + } + + describe("count") { + it("should return count of connections") { + let connection = Connection() + connection.status = "active" + + ConnectionRepository.save(connection) + + expect(viewModel.count).to(equal(1)) + } + } + + describe("hasDataToShow") { + it("must display the data that needs to be shown") { + let connection = Connection() + connection.status = "active" + + ConnectionRepository.save(connection) + + expect(viewModel.hasDataToShow).to(equal(true)) + } + + it("will not display the data to be displayed") { + expect(viewModel.hasDataToShow).to(equal(false)) + } + } + + describe("emptyViewData") { + it("should return correct data") { + let expectedEmptyViewData = EmptyViewData( + image: UIImage(named: "noConnections", in: .authenticator_main, compatibleWith: nil)!, + title: l10n(.noConnections), + description: l10n(.noConnectionsDescription), + buttonTitle: l10n(.connect) + ) + + expect(viewModel.emptyViewData).to(equal(expectedEmptyViewData)) + } + } + + describe("remove(id)") { + it("should remove connection") { + let mockDelegate = MockConnectionsDelegate() + viewModel.delegate = mockDelegate + + let connection = Connection() + connection.id = "id1" + + ConnectionRepository.save(connection) + + viewModel.remove(by: "id1") + + expect(viewModel.count).to(equal(0)) + } + } + + describe("checkInternetAndRemoveConnection") { + context("check internet connection and if internet is off and showConfirmation is true") { + it("should display no internet connection alert") { + let mockDelegate = MockConnectionsDelegate() + viewModel.delegate = mockDelegate + + let connection = Connection() + connection.id = "id1" + + ConnectionRepository.save(connection) + networkSpy.enabled = false + + viewModel.checkInternetAndRemoveConnection(id: "id1", showConfirmation: true) + + expect(mockDelegate.showNoInternetConnectionAlertCall).to(beTrue()) + expect(mockDelegate.showDeleteConfirmationAlertCall).to(beFalse()) + } + } + + context("check internet connection and if internet is off and showConfirmation is false") { + it("should display no internet connection alert") { + let mockDelegate = MockConnectionsDelegate() + viewModel.delegate = mockDelegate + + let connection = Connection() + connection.id = "id1" + + ConnectionRepository.save(connection) + networkSpy.enabled = false + + viewModel.checkInternetAndRemoveConnection(id: "id1", showConfirmation: false) + + expect(mockDelegate.showNoInternetConnectionAlertCall).to(beTrue()) + expect(mockDelegate.showDeleteConfirmationAlertCall).to(beFalse()) + } + } + + context("check internet connection and if internet is on and showConfirmation is true") { + it("should display delete confirmation alert") { + let mockDelegate = MockConnectionsDelegate() + viewModel.delegate = mockDelegate + + let connection = Connection() + connection.id = "id1" + + ConnectionRepository.save(connection) + + networkSpy.enabled = true + + viewModel.checkInternetAndRemoveConnection(id: "id1", showConfirmation: true) + + expect(mockDelegate.showDeleteConfirmationAlertCall).to(beTrue()) + expect(mockDelegate.showNoInternetConnectionAlertCall).to(beFalse()) + } + } + + context("check internet connection and if internet is on and showConfirmation is false") { + it("should remove connection") { + let mockDelegate = MockConnectionsDelegate() + viewModel.delegate = mockDelegate + + let connection = Connection() + connection.id = "id1" + + ConnectionRepository.save(connection) + + networkSpy.enabled = true + + viewModel.checkInternetAndRemoveConnection(id: "id1", showConfirmation: false) + + expect(viewModel.count).to(equal(0)) + expect(mockDelegate.showDeleteConfirmationAlertCall).to(beFalse()) + expect(mockDelegate.showNoInternetConnectionAlertCall).to(beFalse()) + } + } + } + } +} + diff --git a/Example/Tests/v2/JWSHelperSpec.swift b/Example/Tests/v2/JWSHelperSpec.swift new file mode 100644 index 00000000..9a288083 --- /dev/null +++ b/Example/Tests/v2/JWSHelperSpec.swift @@ -0,0 +1,72 @@ +// +// JWSHelperSpec +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Quick +import Nimble +import JOSESwift +import SEAuthenticatorCore +@testable import SEAuthenticatorV2 + +class JWSHelperSpec: BaseSpec { + override func spec() { + let connection = SpecUtils.createConnection(id: "1") + + describe("sign") { + it("should return detached jws signature") { + let expectedMessageDict = ["data": "Test Message"] + + // create actual jws signature without payload + let actualSignature = JWSHelper.sign(params: expectedMessageDict, guid: connection.guid)! + let splittedActualSignature = actualSignature.split(separator: ".") + + // serialize expected payload + let jsonData = ParametersSerializer.createBody(parameters: expectedMessageDict)!.base64EncodedString() + + // insert expected payload into actual jws signature + let final = splittedActualSignature[0] + ".\(jsonData)." + splittedActualSignature[1] + + // verify data + do { + let jws = try JWS(compactSerialization: String(final)) + let verifier = Verifier( + verifyingAlgorithm: .RS256, + publicKey: try SECryptoHelper.publicKey(for: SETagHelper.create(for: connection.guid)) + )! + let payload = try jws.validate(using: verifier).payload + let message = String(data: payload.data(), encoding: .utf8)! + + expect(message.json! == expectedMessageDict).to(beTrue()) + } + } + } + + func jwsSign(payload: [String: String], guid: String) -> String { + let payloadBody = ParametersSerializer.createBody(parameters: payload)! + let privateKey = SpecUtils.privateKey(for: SETagHelper.create(for: guid))! + let signer = Signer(signingAlgorithm: .RS256, key: privateKey)! + + let header = JWSHeader(algorithm: .RS256) + + return try! JWS(header: header, payload: Payload(payloadBody), signer: signer).compactSerializedString + } + } +} diff --git a/Example/Tests/v2/Networking/RequestParametersBuilderSpecV2.swift b/Example/Tests/v2/Networking/RequestParametersBuilderSpecV2.swift new file mode 100644 index 00000000..a32395bb --- /dev/null +++ b/Example/Tests/v2/Networking/RequestParametersBuilderSpecV2.swift @@ -0,0 +1,82 @@ +// +// RequestParametersBuilderSpec +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Quick +import Nimble +import SEAuthenticatorCore +@testable import SEAuthenticatorV2 + +class RequestParametersBuilderSpecV2: BaseSpec { + override func spec() { + describe("confirmAuthorizationParams(for:)") { + it("should return parameters from providerData to obtain token") { + let encryptedData = SEEncryptedData(data: "data", key: "key", iv: "iv") + let expirationTime = Date().addingTimeInterval(5.0 * 60.0).utcSeconds + + let expectedParams: [String: Any] = [ + ParametersKeys.data: [ + ParametersKeys.data: encryptedData.data, + ParametersKeys.key: encryptedData.key, + ParametersKeys.iv: encryptedData.iv + ], + ParametersKeys.exp: expirationTime + ] + + let result = RequestParametersBuilder.confirmAuthorizationParams( + encryptedData: encryptedData, + exp: expirationTime + ) == expectedParams + + expect(result).to(beTruthy()) + } + } + + describe("actionParameters") { + it("should retun correct dict") { + let data = SEActionRequestDataV2( + url: URL(string: "https://url.com")!, + connectionGuid: "123", + accessToken: "456", + appLanguage: "en", + providerId: "1", + actionId: "99", + connectionId: "15" + ) + let expirationTime = Date().addingTimeInterval(5.0 * 60.0).utcSeconds + + let expectedParams: [String: Any] = [ + SENetKeys.data: [ + ParametersKeys.providerId: data.providerId, + ParametersKeys.actionId: data.actionId, + ParametersKeys.connectionId: data.connectionId + ], + ParametersKeys.exp: expirationTime + ] + + let result = RequestParametersBuilder.actionParameters(requestData: data) == expectedParams + + expect(result).to(beTruthy()) + } + } + } +} + diff --git a/Example/Tests/v2/Networking/Routers/SEAuthorizationRouterSpecV2.swift b/Example/Tests/v2/Networking/Routers/SEAuthorizationRouterSpecV2.swift new file mode 100644 index 00000000..f15cbad2 --- /dev/null +++ b/Example/Tests/v2/Networking/Routers/SEAuthorizationRouterSpecV2.swift @@ -0,0 +1,155 @@ +// +// SEAuthorizationRouterSpecV2 +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Quick +import Nimble +import SEAuthenticatorCore +@testable import SEAuthenticatorV2 + +class SEAuthorizationRouterSpecV2: BaseSpec { + override func spec() { + let baseUrl = URL(string: "https://baseUrl.com")! + let baseUrlPath = "/api/authenticator/v2/authorizations" + let accessToken = "access_token" + + describe("AuthorizationsRouter") { + context("when it's .list") { + it("should create a valid url request") { + let data = SEBaseAuthenticatedRequestData( + url: baseUrl, + connectionGuid: "guid", + accessToken: accessToken, + appLanguage: "en" + ) + + let expectedRequest = URLRequestBuilder.buildUrlRequest( + with: baseUrl.appendingPathComponent(baseUrlPath), + method: HTTPMethod.get.rawValue, + headers: Headers.authorizedRequestHeaders(token: accessToken), + encoding: .url + ) + + let request = SEAuthorizationRouter.list(data).asURLRequest() + + expect(request).to(equal(expectedRequest)) + } + } + + context("when it's .show") { + it("should create a valid url request") { + let data = SEBaseAuthenticatedWithIdRequestData( + url: baseUrl, + connectionGuid: "123guid", + accessToken: accessToken, + appLanguage: "en", + entityId: "1" + ) + + let expectedRequest = URLRequestBuilder.buildUrlRequest( + with: baseUrl.appendingPathComponent("\(baseUrlPath)/\(data.entityId)"), + method: HTTPMethod.get.rawValue, + headers: Headers.authorizedRequestHeaders(token: accessToken), + encoding: .url + ) + + let request = SEAuthorizationRouter.show(data).asURLRequest() + + expect(request).to(equal(expectedRequest)) + } + } + + context("when it's .confirm") { + it("should create a valid url request") { + let data = SEConfirmAuthorizationRequestData( + url: baseUrl, + connectionGuid: "123guid", + accessToken: accessToken, + appLanguage: "en", + authorizationId: "1", + authorizationCode: "authorization_code", + geolocation: "GEO:", + authorizationType: "passcode" + ) + + let parameters = RequestParametersBuilder.confirmAuthorizationParams( + encryptedData: SEEncryptedData(data: "data", key: "key", iv: "iv"), + exp: Date().addingTimeInterval(5.0 * 60.0).utcSeconds + ) + + let headers = Headers.signedRequestHeaders( + token: accessToken, + payloadParams: parameters, + connectionGuid: "123guid" + ) + + let expectedRequest = URLRequestBuilder.buildUrlRequest( + with: baseUrl.appendingPathComponent("\(baseUrlPath)/\(data.entityId)/confirm"), + method: HTTPMethod.put.rawValue, + headers: headers, + params: parameters, + encoding: .json + ) + + let request = SEAuthorizationRouter.confirm(data, parameters).asURLRequest() + + expect(request).to(equal(expectedRequest)) + } + } + + context("when it's .deny") { + it("should create a valid url request") { + let data = SEConfirmAuthorizationRequestData( + url: baseUrl, + connectionGuid: "123guid", + accessToken: accessToken, + appLanguage: "en", + authorizationId: "1", + authorizationCode: "authorization_code", + geolocation: "GEO:", + authorizationType: "passcode" + ) + let parameters = RequestParametersBuilder.confirmAuthorizationParams( + encryptedData: SEEncryptedData(data: "data", key: "key", iv: "iv"), + exp: Date().addingTimeInterval(5.0 * 60.0).utcSeconds + ) + let headers = Headers.signedRequestHeaders( + token: accessToken, + payloadParams: parameters, + connectionGuid: "123guid" + ) + + let expectedRequest = URLRequestBuilder.buildUrlRequest( + with: baseUrl.appendingPathComponent("\(baseUrlPath)/\(data.entityId)/deny"), + method: HTTPMethod.put.rawValue, + headers: headers, + params: parameters, + encoding: .json + ) + + let request = SEAuthorizationRouter.deny(data, parameters).asURLRequest() + + expect(request).to(equal(expectedRequest)) + } + } + } + } +} diff --git a/Example/Tests/v2/Networking/Routers/SEConnectionRouterSpecV2.swift b/Example/Tests/v2/Networking/Routers/SEConnectionRouterSpecV2.swift new file mode 100644 index 00000000..b2cdc5be --- /dev/null +++ b/Example/Tests/v2/Networking/Routers/SEConnectionRouterSpecV2.swift @@ -0,0 +1,97 @@ +// +// SEConnectionRouterSpecV2 +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Quick +import Nimble +import SEAuthenticatorCore +@testable import SEAuthenticatorV2 + +class SEConnectionRouterSpecV2: BaseSpec { + override func spec() { + let baseUrl = URL(string: "https://baseUrl.com")! + let baseUrlPath = "/api/authenticator/v2/connections" + + describe("ConnectionRouter") { + context(".createConnection") { + it("should create a valid url request") { + let encryptedData = SEEncryptedData(data: "data", key: "key", iv: "iv") + let data = SECreateConnectionParams( + providerId: "12", + pushToken: "push_token", + connectQuery: "connect_query", + encryptedRsaPublicKey: encryptedData + ) + + let expectedRequest = URLRequestBuilder.buildUrlRequest( + with: baseUrl.appendingPathComponent(baseUrlPath), + method: HTTPMethod.post.rawValue, + headers: Headers.requestHeaders(with: "en"), + params: RequestParametersBuilder.parameters(for: data), + encoding: .json + ) + + let actualRequest = SEConnectionRouter.createConnection(baseUrl, data, "en").asURLRequest() + + expect(actualRequest).to(equal(expectedRequest)) + } + } + + context(".revoke") { + it("should create a valid url request") { + let connectionGuid = "guid" + let entityId = "1" + let accessToken = "access_token" + let parameters: [String : Any] = [ + ParametersKeys.data: [:], + ParametersKeys.exp: Date().addingTimeInterval(5.0 * 60.0).utcSeconds + ] + + let headers = Headers.signedRequestHeaders( + token: accessToken, + payloadParams: parameters, + connectionGuid: connectionGuid + ) + + let expectedRequest = URLRequestBuilder.buildUrlRequest( + with: baseUrl.appendingPathComponent("/api/authenticator/v2/connections/\(entityId)/revoke"), + method: HTTPMethod.put.rawValue, + headers: headers, + params: parameters + ) + + let actualRequest = SEConnectionRouter.revoke( + SEBaseAuthenticatedWithIdRequestData( + url: baseUrl, + connectionGuid: connectionGuid, + accessToken: accessToken, + appLanguage: "en", + entityId: entityId + ) + ).asURLRequest() + + expect(actualRequest).to(equal(expectedRequest)) + } + } + } + } +} + diff --git a/Gemfile.lock b/Gemfile.lock index 02e855af..70542469 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -32,6 +32,8 @@ GEM nokogiri (1.11.3) mini_portile2 (~> 2.5.0) racc (~> 1.4) + nokogiri (1.11.3-arm64-darwin) + racc (~> 1.4) nokogiri (1.11.3-x86_64-darwin) racc (~> 1.4) pry (0.14.1) @@ -68,6 +70,7 @@ GEM PLATFORMS -darwin-21 + universal-darwin-20 x86_64-darwin-19 DEPENDENCIES diff --git a/README.md b/README.md index fee2af60..985d8f23 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,13 @@ or build from source code. 1. Move to project directory `sca-authenticator-ios/Example` 1. Command in terminal: `bundle install` (To install all required gems) 1. Command in terminal: `pod install` (To install all required pods) + + If `pod install` fails on M1 Macs, do next: + ``` + sudo arch -x86_64 gem install ffi + arch -x86_64 pod update + ``` + Also make sure you set the `arm64` values in the "Build Settings" -> "Architectures" -> "Excluded Architectures". 1. Open project's workspace file in Xcode (`Example/Authenticator.xcworkspace`) 1. Create `application.plist` configuration file using `application.example.plist` 1. If you have intent to use Firebase Crashlytics then generate `GoogleService-info.plist` and add it to project. diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/BaseStructures.swift b/SaltedgeAuthenticatorCore/Classes/API/Models/BaseStructures.swift similarity index 90% rename from SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/BaseStructures.swift rename to SaltedgeAuthenticatorCore/Classes/API/Models/BaseStructures.swift index 30851bed..ab6c9743 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/BaseStructures.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/Models/BaseStructures.swift @@ -1,8 +1,8 @@ // -// BaseStructures.swift +// BaseStructures // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2020 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -22,7 +22,7 @@ import Foundation -public class SEBaseAuthenticatedRequestData { +open class SEBaseAuthenticatedRequestData { public let url: URL public let connectionGuid: GUID public let accessToken: AccessToken @@ -41,7 +41,7 @@ public class SEBaseAuthenticatedRequestData { } } -public class SEBaseAuthenticatedWithIdRequestData: SEBaseAuthenticatedRequestData { +open class SEBaseAuthenticatedWithIdRequestData: SEBaseAuthenticatedRequestData { public let entityId: ID public init( diff --git a/SaltedgeAuthenticatorSDK/Classes/API/BaseNetworking.swift b/SaltedgeAuthenticatorCore/Classes/API/Models/SEBaseActionResponse.swift similarity index 78% rename from SaltedgeAuthenticatorSDK/Classes/API/BaseNetworking.swift rename to SaltedgeAuthenticatorCore/Classes/API/Models/SEBaseActionResponse.swift index 2a155d84..022b2b9c 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/BaseNetworking.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/Models/SEBaseActionResponse.swift @@ -1,8 +1,8 @@ // -// BaseNetworking.swift +// SEBaseActionResponse // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -22,8 +22,7 @@ import Foundation -protocol Networking { - static func execute(_ urlRequest: Routable, success: @escaping RequestSuccessBlock, failure: @escaping FailureBlock) +public protocol SEBaseActionResponse: Decodable { + var authorizationId: String? { get } + var connectionId: String? { get } } - -struct BaseNetworking: Networking {} diff --git a/SaltedgeAuthenticatorSDK/Classes/API/SENetPaths.swift b/SaltedgeAuthenticatorCore/Classes/API/Models/SEBaseAuthorizationData.swift similarity index 73% rename from SaltedgeAuthenticatorSDK/Classes/API/SENetPaths.swift rename to SaltedgeAuthenticatorCore/Classes/API/Models/SEBaseAuthorizationData.swift index 072eb40e..544d6be4 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/SENetPaths.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/Models/SEBaseAuthorizationData.swift @@ -1,8 +1,8 @@ // -// SENetPaths.swift +// SEBaseAuthorizationData // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -22,17 +22,11 @@ import Foundation -public enum SENetPaths: String { - case connections - case authorizations - case actions - case consents - - public var path: String { - return "/api/authenticator/v\(version)/\(rawValue)" - } - - private var version: Int { - return 1 - } +public protocol SEBaseAuthorizationData { + var id: String { get } + var connectionId: String { get } + var createdAt: Date { get } + var expiresAt: Date { get } + var authorizationCode: String? { get } + var apiVersion: ApiVersion { get } } diff --git a/SaltedgeAuthenticatorCore/Classes/API/Models/SEBaseEncryptedAuthorizationData.swift b/SaltedgeAuthenticatorCore/Classes/API/Models/SEBaseEncryptedAuthorizationData.swift new file mode 100644 index 00000000..76ce0251 --- /dev/null +++ b/SaltedgeAuthenticatorCore/Classes/API/Models/SEBaseEncryptedAuthorizationData.swift @@ -0,0 +1,31 @@ +// +// SEBaseEncryptedAuthorizationData +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation + +public protocol SEBaseEncryptedAuthorizationData { + var data: String { get } + var key: String { get } + var iv: String { get } + var connectionId: String? { get } + var entityId: String? { get } +} diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/SEConfirmAuthorizationRequestData.swift b/SaltedgeAuthenticatorCore/Classes/API/Models/SEConfirmAuthorizationData.swift similarity index 95% rename from SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/SEConfirmAuthorizationRequestData.swift rename to SaltedgeAuthenticatorCore/Classes/API/Models/SEConfirmAuthorizationData.swift index aefea34f..aa93871e 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/SEConfirmAuthorizationRequestData.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/Models/SEConfirmAuthorizationData.swift @@ -1,8 +1,8 @@ // -// SEConfirmAuthorizationRequestData.swift +// SEConfirmAuthorizationData // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -49,4 +49,3 @@ public class SEConfirmAuthorizationRequestData: SEBaseAuthenticatedWithIdRequest ) } } - diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/SEConsentData.swift b/SaltedgeAuthenticatorCore/Classes/API/Models/SEConsentData.swift similarity index 87% rename from SaltedgeAuthenticatorSDK/Classes/API/Models/SEConsentData.swift rename to SaltedgeAuthenticatorCore/Classes/API/Models/SEConsentData.swift index 344fd526..990fa6ed 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/SEConsentData.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/Models/SEConsentData.swift @@ -1,8 +1,8 @@ // -// SEConsentData.swift +// SEConsentData // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2020 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -23,8 +23,8 @@ import Foundation public struct SEConsentData { - public let id: String - public let userId: String + public var id: String? + public var userId: String? public let connectionId: String public let tppName: String public let consentType: String @@ -33,16 +33,12 @@ public struct SEConsentData { public let createdAt: Date public let expiresAt: Date - public init?(_ dictionary: [String: Any], _ connectionId: String) { - if let id = dictionary[SENetKeys.id] as? String, - let userId = dictionary[SENetKeys.userId] as? String, - let tppName = dictionary[SENetKeys.tppName] as? String, + public init?(_ dictionary: [String: Any], _ entityId: String?, _ connectionId: String) { + if let tppName = dictionary[SENetKeys.tppName] as? String, let consentType = dictionary[SENetKeys.consentType] as? String, let accountsObjects = dictionary[SENetKeys.accounts] as? [[String: Any]], let createdAt = (dictionary[SENetKeys.createdAt] as? String)?.iso8601date, let expiresAt = (dictionary[SENetKeys.expiresAt] as? String)?.iso8601date { - self.id = id - self.userId = userId self.tppName = tppName self.consentType = consentType self.createdAt = createdAt @@ -54,6 +50,15 @@ public struct SEConsentData { self.sharedData = SEConsentSharedData((dictionary[SENetKeys.sharedData] as? [String: Bool])) self.connectionId = connectionId + + if let userId = dictionary[SENetKeys.userId] as? String { + self.userId = userId + } + if let entityId = entityId { + self.id = entityId + } else if let id = dictionary[SENetKeys.id] as? String { + self.id = id + } } else { return nil } @@ -117,3 +122,4 @@ extension SEConsentSharedData: Equatable { return lhs.balance == rhs.balance && lhs.transactions == rhs.transactions } } + diff --git a/SaltedgeAuthenticatorCore/Classes/API/Networking/Networking.swift b/SaltedgeAuthenticatorCore/Classes/API/Networking/Networking.swift new file mode 100644 index 00000000..9b6bc257 --- /dev/null +++ b/SaltedgeAuthenticatorCore/Classes/API/Networking/Networking.swift @@ -0,0 +1,107 @@ +// +// Networking +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation + +public struct HTTPService { + public static func makeRequest( + _ request: Routable, + completion: SEHTTPResponse, + failure: @escaping SimpleFailureClosure + ) { + makeRequest(request.asURLRequest(), completion: completion, failure: failure) + } + + private static func makeRequest( + _ request: URLRequest, + completion: SEHTTPResponse, + failure: @escaping SimpleFailureClosure + ) { +// NOTE: Uncomment this to debug request +// let urlString = request.url?.absoluteString ?? "" +// print("🚀 Running request: \(request.httpMethod ?? "") - \(urlString)") + + let task = URLSessionManager.shared.dataTask(with: request) { data, _, error in + let decoder = JSONDecoder() + decoder.dateDecodingStrategyFormatters = [DateUtils.dateFormatter, DateUtils.ymdDateFormatter] + + let (data, error) = handleResponse(from: data, error: error, decoder: decoder) + + guard let jsonData = data, error == nil else { + DispatchQueue.main.async { failure(error!.localizedDescription) } + return + } + +// NOTE: Uncomment this to debug response data +// print("Response: ", String(data: jsonData, encoding: .utf8)) + + do { + let model = try decoder.decode(T.self, from: jsonData) + DispatchQueue.main.async { completion?(model) } + } catch { + DispatchQueue.main.async { failure(error.localizedDescription) } + } + } + task.resume() + } + + private static func handleResponse(from data: Data?, error: Error?, decoder: JSONDecoder) -> (Data?, Error?) { + if let error = error { return (nil, error) } + + guard let jsonData = data else { + // -1017 -- The connection cannot parse the server’s response. + let error = NSError( + domain: "", + code: -1017, + userInfo: [NSLocalizedDescriptionKey: "Data was not retrieved from request"] + ) as Error + return (nil, error) + } + + return (jsonData, nil) + } +} + +private extension JSONDecoder { + var dateDecodingStrategyFormatters: [DateFormatter]? { + get { + return nil + } + set { + guard let formatters = newValue else { return } + + self.dateDecodingStrategy = .custom { decoder in + let container = try decoder.singleValueContainer() + let dateString = try container.decode(String.self) + + for formatter in formatters { + if let date = formatter.date(from: dateString) { + return date + } + } + + throw DecodingError.dataCorruptedError(in: container, debugDescription: "Cannot decode date string \(dateString)") + } + } + } +} + diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Managers/URLSessionManager.swift b/SaltedgeAuthenticatorCore/Classes/API/Networking/URLSessionManager.swift similarity index 95% rename from SaltedgeAuthenticatorSDK/Classes/API/Managers/URLSessionManager.swift rename to SaltedgeAuthenticatorCore/Classes/API/Networking/URLSessionManager.swift index ef525218..309d3ea4 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Managers/URLSessionManager.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/Networking/URLSessionManager.swift @@ -1,8 +1,8 @@ // -// URLSessionManager.swift +// URLSessionManager // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by diff --git a/SaltedgeAuthenticatorSDK/Classes/API/SEConnectHelper.swift b/SaltedgeAuthenticatorCore/Classes/API/SEConnectHelper.swift similarity index 56% rename from SaltedgeAuthenticatorSDK/Classes/API/SEConnectHelper.swift rename to SaltedgeAuthenticatorCore/Classes/API/SEConnectHelper.swift index 6e1fb485..b7ba12ea 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/SEConnectHelper.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/SEConnectHelper.swift @@ -1,8 +1,8 @@ // -// SEConnectHelper.swift +// SEConnectHelper // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -28,23 +28,45 @@ public struct SEConnectHelper { } public static func isValidAction(deepLinkUrl url: URL) -> Bool { - return actionGuid(from: url) != nil && connectUrl(from: url) != nil + if let apiVersion = apiVersion(from: url), apiVersion == "2" { + return actionId(from: url) != nil && providerId(from: url) != nil + } else { + return actionGuid(from: url) != nil && connectUrl(from: url) != nil + } } public static func returnToUrl(from url: URL) -> URL? { - guard let query = url.queryItem(for: "return_to") else { return nil } + guard let query = url.queryItem(for: SENetKeys.returnTo) else { return nil } return URL(string: query) } public static func connectUrl(from url: URL) -> URL? { - guard let query = url.queryItem(for: "connect_url") else { return nil } + guard let query = url.queryItem(for: SENetKeys.connectUrl) else { return nil } return URL(string: query) } + public static func apiVersion(from url: URL) -> String? { + guard let query = url.queryItem(for: SENetKeys.apiVersion) else { return nil } + + return query + } + + public static func actionId(from url: URL) -> String? { + guard let query = url.queryItem(for: SENetKeys.actionId) else { return nil } + + return query + } + + public static func providerId(from url: URL) -> String? { + guard let query = url.queryItem(for: SENetKeys.providerId) else { return nil } + + return query + } + public static func actionGuid(from url: URL) -> String? { - guard let query = url.queryItem(for: "action_uuid") else { return nil } + guard let query = url.queryItem(for: SENetKeys.actionUuid) else { return nil } return query } @@ -60,4 +82,10 @@ public struct SEConnectHelper { return query } + + public static func shouldStartInstantActionFlow(url: URL) -> Bool { + return SEConnectHelper.actionId(from: url) != nil && SEConnectHelper.providerId(from: url) != nil || + SEConnectHelper.actionGuid(from: url) != nil && SEConnectHelper.connectUrl(from: url) != nil + } } + diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/SEEncryptedData.swift b/SaltedgeAuthenticatorCore/Classes/API/SEEncryptedData.swift similarity index 60% rename from SaltedgeAuthenticatorSDK/Classes/API/Models/SEEncryptedData.swift rename to SaltedgeAuthenticatorCore/Classes/API/SEEncryptedData.swift index 40ec80c4..293a428c 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/SEEncryptedData.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/SEEncryptedData.swift @@ -2,7 +2,7 @@ // SEEncryptedData.swift // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2020 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -22,13 +22,14 @@ import Foundation -public struct SEEncryptedData: SerializableResponse, Equatable { +public struct SEEncryptedData: SEBaseEncryptedAuthorizationData, Decodable, Equatable { private let defaultAlgorithm = "AES-256-CBC" public let data: String public let key: String public let iv: String public var connectionId: String? + public var entityId: String? public init(data: String, key: String, iv: String) { self.data = data @@ -37,17 +38,22 @@ public struct SEEncryptedData: SerializableResponse, Equatable { } public init?(_ value: Any) { + print("VALUE: \(value as? [String: Any])") + if let dict = value as? [String: Any], let data = dict[SENetKeys.data] as? String, let key = dict[SENetKeys.key] as? String, - let iv = dict[SENetKeys.iv] as? String, - let algorithm = dict[SENetKeys.algorithm] as? String, - algorithm == defaultAlgorithm { + let iv = dict[SENetKeys.iv] as? String { self.data = data self.key = key self.iv = iv + if let entityId = dict[SENetKeys.id] as? Int { + self.entityId = "\(entityId)" + } if let connectionId = dict[SENetKeys.connectionId] as? String { self.connectionId = connectionId + } else if let connectionId = dict[SENetKeys.connectionId] as? Int { // NOTE: connection_id in v2 is integer + self.connectionId = "\(connectionId)" } } else { return nil @@ -61,3 +67,29 @@ public struct SEEncryptedData: SerializableResponse, Equatable { lhs.connectionId == rhs.connectionId } } + +public struct SEEncryptedDataResponse: Decodable { + public var data: SEEncryptedData + + enum CodingKeys: String, CodingKey { + case data + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + data = try container.decode(SEEncryptedData.self, forKey: .data) + } +} + +public struct SEEncryptedListResponse: Decodable { + public var data: [SEEncryptedData] = [] + + enum CodingKeys: String, CodingKey { + case data + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + data = try container.decode([SEEncryptedData].self, forKey: .data) + } +} diff --git a/SaltedgeAuthenticatorSDK/Classes/API/SENetConstants.swift b/SaltedgeAuthenticatorCore/Classes/API/SENetConstants.swift similarity index 84% rename from SaltedgeAuthenticatorSDK/Classes/API/SENetConstants.swift rename to SaltedgeAuthenticatorCore/Classes/API/SENetConstants.swift index 3dc67cfe..3202b266 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/SENetConstants.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/SENetConstants.swift @@ -2,7 +2,7 @@ // SENetConstants.swift // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -22,12 +22,12 @@ import Foundation -struct SENetConstants { - static var oauthRedirectUrl: String { +public struct SENetConstants { + public static var oauthRedirectUrl: String { return "authenticator://oauth/redirect" } - static func hasRedirectUrl(_ urlString: String) -> Bool { + public static func hasRedirectUrl(_ urlString: String) -> Bool { return urlString.starts(with: oauthRedirectUrl) } } diff --git a/SaltedgeAuthenticatorSDK/Classes/API/SENetKeys.swift b/SaltedgeAuthenticatorCore/Classes/API/SENetKeys.swift similarity index 75% rename from SaltedgeAuthenticatorSDK/Classes/API/SENetKeys.swift rename to SaltedgeAuthenticatorCore/Classes/API/SENetKeys.swift index f760692a..de703f2a 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/SENetKeys.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/SENetKeys.swift @@ -2,7 +2,7 @@ // SENetKeys.swift // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -23,6 +23,8 @@ import Foundation public struct SENetKeys { + public static let apiVersion = "api_version" + public static let aps = "aps" public static let data = "data" @@ -43,6 +45,7 @@ public struct SENetKeys { public static let message = "message" public static let success = "success" + public static let status = "status" public static let accessToken = "access_token" @@ -50,6 +53,8 @@ public struct SENetKeys { public static let expiresAt = "expires_at" public static let redirectUrl = "redirect_url" + public static let returnTo = "return_to" + public static let key = "key" public static let iv = "iv" @@ -75,4 +80,23 @@ public struct SENetKeys { public static let iban = "iban" public static let balance = "balance" public static let transactions = "transactions" + + public static let payment = "payment" + public static let extra = "extra" + public static let text = "text" + public static let payee = "payee" + public static let amount = "amount" + public static let account = "account" + public static let paymentDate = "payment_date" + public static let reference = "reference" + public static let fee = "fee" + public static let exchangeRate = "exchange_rate" + public static let actionDate = "action_date" + public static let device = "device" + public static let location = "location" + public static let ip = "ip" + + public static let actionId = "action_id" + public static let actionUuid = "action_uuid" + public static let providerId = "provider_id" } diff --git a/Example/Authenticator/Utils/Helpers/ParametersSerializer.swift b/SaltedgeAuthenticatorCore/Classes/API/SENetPathBuilder.swift similarity index 68% rename from Example/Authenticator/Utils/Helpers/ParametersSerializer.swift rename to SaltedgeAuthenticatorCore/Classes/API/SENetPathBuilder.swift index 81adad73..c7c1e45e 100644 --- a/Example/Authenticator/Utils/Helpers/ParametersSerializer.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/SENetPathBuilder.swift @@ -1,8 +1,8 @@ // -// ParametersSerializer.swift +// SENetPathBuilder // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -22,16 +22,16 @@ import Foundation -struct ParametersSerializer { - static func createBody(parameters: [String: Any]) -> Data? { - do { - let data = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted) - - return data - } catch { - Log.debugLog(message: error.localizedDescription) - } +public struct SENetPathBuilder { + public enum Endpoints: String { + case connections + case authorizations + case actions + case consents + } + public var path: String - return nil + public init(for endpoint: Endpoints, version: Int = 1) { + path = "/api/authenticator/v\(version)/\(endpoint.rawValue)" } } diff --git a/SaltedgeAuthenticatorSDK/Classes/Crypto/SECryptoHelper.swift b/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelper.swift similarity index 82% rename from SaltedgeAuthenticatorSDK/Classes/Crypto/SECryptoHelper.swift rename to SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelper.swift index 95d11da5..d217d9f1 100644 --- a/SaltedgeAuthenticatorSDK/Classes/Crypto/SECryptoHelper.swift +++ b/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelper.swift @@ -2,7 +2,7 @@ // SECryptoHelper.swift // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -28,6 +28,14 @@ public typealias KeyPair = (publicKey: SecKey, privateKey: SecKey) public struct SECryptoHelper { // MARK: - Public Methods + + /* + Create RSA key pair and store it in Keychain + + - parameters: + - tag: An unique identifier for a newly generated key pair, by which the key could be retrieved from Keychain + */ + @discardableResult public static func createKeyPair(with tag: KeyTag) -> KeyPair? { SecKeyHelper.deleteKey(tag) SecKeyHelper.deleteKey(tag.privateTag) @@ -35,11 +43,40 @@ public struct SECryptoHelper { return SecKeyHelper.generateKeyPair(tag: tag) } - public static func createKey(fromFile name: String, isPublic: Bool, tag: String) throws -> SecKey { + /* + Convert private key from asymmetric key pair to pem string + + - parameters: + - tag: An unique identifier by which was created the private key + */ + public static func privateKeyToPem(tag: KeyTag) -> String? { + return try? privateKeyData(for: tag.privateTag).string + } + + /* + Convert public key from asymmetric key pair to pem string + + - parameters: + - tag: An unique identifier by which was created the public key + */ + public static func publicKeyToPem(tag: KeyTag) -> String? { + return try? publicKeyData(for: tag).string + } + + /* + Converts string which contains private key in PEM format to SecKey object + + - parameters: + - pem: Key in pem format + - isPublic: Type of the key + - tag: An unique identifier by which the key could be retrieved from Keychain + */ + @discardableResult + public static func createKey(from pem: String, isPublic: Bool, tag: String) -> SecKey? { SecKeyHelper.deleteKey(tag) SecKeyHelper.deleteKey(tag.privateTag) - return try SecKeyHelper.createKey(fromFile: name, isPublic: isPublic, tag: tag) + return try? SecKeyHelper.createKey(from: pem, isPublic: isPublic, tag: tag) } @discardableResult @@ -63,23 +100,43 @@ public struct SECryptoHelper { ) } - public static func decrypt(_ encryptedData: SEEncryptedData, tag: KeyTag) throws -> String { + public static func decrypt(_ encryptedData: SEBaseEncryptedAuthorizationData, tag: KeyTag) throws -> String { let privateKey = try SecKeyHelper.obtainKey(for: tag.privateTag) - return try decrypt(encryptedData, privateKey: privateKey) - } - - public static func decrypt(_ encryptedData: SEEncryptedData, privateKey: SecKey) throws -> String { let decryptedKey = try privateDecrypt(message: encryptedData.key, privateKey: privateKey) let decryptedIv = try privateDecrypt(message: encryptedData.iv, privateKey: privateKey) return try AesCipher.decrypt(data: encryptedData.data, key: decryptedKey, iv: decryptedIv) } + + public static func decrypt(key: String, tag: KeyTag) throws -> String { + let privateKey = try SecKeyHelper.obtainKey(for: tag.privateTag) + + do { + let decryptedData = try privateDecrypt(message: key, privateKey: privateKey) + + guard let result = String(data: decryptedData, encoding: .utf8) else { + throw SEAesCipherError.couldNotCreateDecodedString(fromData: decryptedData) + } + + return result + } catch { + throw error + } + } public static func publicKeyData(for tag: KeyTag) throws -> Data { return try SecKeyHelper.obtainKeyData(for: tag) } + public static func privateKeyData(for tag: KeyTag) throws -> Data { + return try SecKeyHelper.obtainKeyData(for: tag.privateTag) + } + + public static func publicKey(for tag: KeyTag) throws -> SecKey { + return try SecKeyHelper.obtainKey(for: tag) + } + public static func privateKey(for tag: KeyTag) throws -> SecKey { return try SecKeyHelper.obtainKey(for: tag.privateTag) } @@ -115,7 +172,10 @@ public struct SECryptoHelper { idx += blockSize } - return Data(bytes: UnsafePointer(decryptedDataBytes), count: decryptedDataBytes.count) + let uint8Pointer = UnsafeMutablePointer.allocate(capacity: decryptedDataBytes.count) + uint8Pointer.initialize(from: &decryptedDataBytes, count: decryptedDataBytes.count) + + return Data(bytes: uint8Pointer, count: decryptedDataBytes.count) } private static func publicEncrypt(data: Data, keyForTag: KeyTag) throws -> Data { @@ -148,7 +208,10 @@ public struct SECryptoHelper { idx += maxChunkSize } - return Data(bytes: UnsafePointer(encryptedDataBytes), count: encryptedDataBytes.count) + let uint8Pointer = UnsafeMutablePointer.allocate(capacity: encryptedDataBytes.count) + uint8Pointer.initialize(from: &encryptedDataBytes, count: encryptedDataBytes.count) + + return Data(bytes: uint8Pointer, count: encryptedDataBytes.count) } private static func generateRandomBytes(count: Int) throws -> Data { @@ -240,8 +303,8 @@ private struct SecKeyHelper { return nil } - static func createKey(fromFile name: String, isPublic: Bool, tag: String) throws -> SecKey { - let base64Encoded = base64String(pemEncoded: pem(name)) + static func createKey(from pem: String, isPublic: Bool, tag: String) throws -> SecKey { + let base64Encoded = base64String(pemEncoded: pem) guard let data = Data(base64Encoded: base64Encoded, options: [.ignoreUnknownCharacters]) else { throw SECryptoHelperError.errorCreatingData(fromBase64: base64Encoded) @@ -322,7 +385,7 @@ private struct SecKeyHelper { static func base64String(pemEncoded pemString: String) -> String { return pemString.components(separatedBy: "\n").filter { line in return !line.hasPrefix("-----BEGIN") && !line.hasPrefix("-----END") - }.joined(separator: "") + }.joined(separator: "") } static func pem(_ filename: String) -> String { @@ -334,7 +397,7 @@ private struct SecKeyHelper { } // MARK: - Public Extensions -extension Data { +public extension Data { var string: String { let beginPublicKey = "-----BEGIN PUBLIC KEY-----\n" let endPublicKey = "\n-----END PUBLIC KEY-----\n" @@ -346,7 +409,7 @@ extension Data { } // MARK: - Private Extensions -extension Data { +public extension Data { func dataByPrependingX509Header() -> Data { let result = NSMutableData() diff --git a/SaltedgeAuthenticatorSDK/Classes/Crypto/SECryptoHelperError.swift b/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelperError.swift similarity index 95% rename from SaltedgeAuthenticatorSDK/Classes/Crypto/SECryptoHelperError.swift rename to SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelperError.swift index 7f63852d..724e4b9e 100644 --- a/SaltedgeAuthenticatorSDK/Classes/Crypto/SECryptoHelperError.swift +++ b/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelperError.swift @@ -23,7 +23,7 @@ import Foundation -enum SECryptoHelperError: Error { +public enum SECryptoHelperError: Error { case errorGeneratingRandomBytes case errorCreatingData(fromBase64: String) case couldNotEncryptChunk(at: Int) @@ -45,7 +45,7 @@ enum SECryptoHelperError: Error { } } -enum SEAesCipherError: Error { +public enum SEAesCipherError: Error { case couldNotCreateData(from: String) case couldNotCreateString(fromBase64: String) case couldNotCreateEncryptedData(fromBase64: String) @@ -70,7 +70,7 @@ enum SEAesCipherError: Error { } } -enum SESecKeyHelperError: Error { +public enum SESecKeyHelperError: Error { case couldNotObtainKey(for: String) case couldNotObtainKeyData(for: String) case couldNotAddToKeychain diff --git a/SaltedgeAuthenticatorSDK/Classes/Crypto/SETagHelper.swift b/SaltedgeAuthenticatorCore/Classes/Crypto/SETagHelper.swift similarity index 100% rename from SaltedgeAuthenticatorSDK/Classes/Crypto/SETagHelper.swift rename to SaltedgeAuthenticatorCore/Classes/Crypto/SETagHelper.swift diff --git a/SaltedgeAuthenticatorSDK/Classes/Extensions/DateExtensions.swift b/SaltedgeAuthenticatorCore/Classes/DateExtensions.swift similarity index 71% rename from SaltedgeAuthenticatorSDK/Classes/Extensions/DateExtensions.swift rename to SaltedgeAuthenticatorCore/Classes/DateExtensions.swift index 05cd64e4..003e4c94 100644 --- a/SaltedgeAuthenticatorSDK/Classes/Extensions/DateExtensions.swift +++ b/SaltedgeAuthenticatorCore/Classes/DateExtensions.swift @@ -1,8 +1,8 @@ // -// DateExtensions.swift +// DateExtensions // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -33,6 +33,24 @@ public extension Date { return Int(dateFromComponents.timeIntervalSince1970) } + var withoutTime: Date { + var currentCalendar = Calendar.current + currentCalendar.timeZone = TimeZone.utc + let components = Calendar.current.dateComponents([.day, .month, .year], from: self) + return currentCalendar.date(from: components)! + } + + var numberOfDaysFromNow: Int { + let calendar = Calendar.current + + let date1 = calendar.startOfDay(for: Date()) + let date2 = calendar.startOfDay(for: self) + + let components = calendar.dateComponents([.day], from: date1, to: date2) + + return components.day ?? 0 + } + func get(_ component: Calendar.Component, calendar: Calendar = Calendar.current) -> Int { return calendar.component(component, from: self) } diff --git a/SaltedgeAuthenticatorSDK/Classes/Extensions/DictionaryExtensions.swift b/SaltedgeAuthenticatorCore/Classes/DictionaryExtensions.swift similarity index 95% rename from SaltedgeAuthenticatorSDK/Classes/Extensions/DictionaryExtensions.swift rename to SaltedgeAuthenticatorCore/Classes/DictionaryExtensions.swift index a3b1edca..bc0928b8 100644 --- a/SaltedgeAuthenticatorSDK/Classes/Extensions/DictionaryExtensions.swift +++ b/SaltedgeAuthenticatorCore/Classes/DictionaryExtensions.swift @@ -2,7 +2,7 @@ // DictionaryExtensions.swift // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -22,7 +22,7 @@ import Foundation -extension Dictionary { +public extension Dictionary { func merge(with other: Dictionary) -> Dictionary { var copy = self for (k, v) in other { diff --git a/SaltedgeAuthenticatorCore/Classes/Helpers/ApiVersionExtractor.swift b/SaltedgeAuthenticatorCore/Classes/Helpers/ApiVersionExtractor.swift new file mode 100644 index 00000000..6b34d101 --- /dev/null +++ b/SaltedgeAuthenticatorCore/Classes/Helpers/ApiVersionExtractor.swift @@ -0,0 +1,34 @@ +// +// ConnectAppLinkData +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation + +public extension String { + var apiVerion: String { + let segments = split(separator: "/") + + guard let index = segments.firstIndex(of: "authenticator"), + let apiVersionValue = Int(segments[index + 1].replacingOccurrences(of: "v", with: "")) else { return "1" } + + return "\(apiVersionValue)" + } +} diff --git a/SaltedgeAuthenticatorCore/Classes/Helpers/AuthorizationStatus.swift b/SaltedgeAuthenticatorCore/Classes/Helpers/AuthorizationStatus.swift new file mode 100644 index 00000000..dda92d8d --- /dev/null +++ b/SaltedgeAuthenticatorCore/Classes/Helpers/AuthorizationStatus.swift @@ -0,0 +1,52 @@ +// +// AuthorizationStatus +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation + +public enum AuthorizationStatus: String, Decodable { + case pending + case processing + case confirmed + case denied + case error + case timeOut = "time_out" + case unavailable + case confirmProcessing = "confirm_processing" + case denyProcessing = "deny_processing" + case closed + + public var isFinal: Bool { + return self == .confirmed + || self == .denied + || self == .error + || self == .timeOut + || self == .unavailable + } + + public var isProcessing: Bool { + return self == .confirmProcessing || self == .denyProcessing + } + + public var isClosed: Bool { + return self == .closed + } +} diff --git a/SaltedgeAuthenticatorSDK/Classes/Helpers/DateUtils.swift b/SaltedgeAuthenticatorCore/Classes/Helpers/DateUtils.swift similarity index 66% rename from SaltedgeAuthenticatorSDK/Classes/Helpers/DateUtils.swift rename to SaltedgeAuthenticatorCore/Classes/Helpers/DateUtils.swift index a1d50bd1..9ebd6379 100644 --- a/SaltedgeAuthenticatorSDK/Classes/Helpers/DateUtils.swift +++ b/SaltedgeAuthenticatorCore/Classes/Helpers/DateUtils.swift @@ -1,8 +1,8 @@ // -// DateUtils.swift +// DateUtils // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -27,6 +27,14 @@ public struct DateUtils { return shared.iso8601dateFormatter } + public static var dateFormatter: DateFormatter { + return shared.dateFormatter + } + + public static var ymdDateFormatter: DateFormatter { + return shared.ymdDateFormatter + } + private static let shared = DateUtils() private var iso8601dateFormatter: ISO8601DateFormatter = { @@ -35,6 +43,22 @@ public struct DateUtils { formatter.formatOptions = [.withFullDate, .withTime, .withDashSeparatorInDate, .withColonSeparatorInTime] return formatter }() + + fileprivate var ymdDateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy'-'MM'-'dd" + formatter.timeZone = TimeZone.utc + return formatter + }() + + fileprivate var dateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.calendar = Calendar(identifier: .iso8601) + formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX" + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.timeZone = TimeZone.utc + return formatter + }() } public extension String { diff --git a/SaltedgeAuthenticatorCore/Classes/Helpers/DeviceModelExtension.swift b/SaltedgeAuthenticatorCore/Classes/Helpers/DeviceModelExtension.swift new file mode 100644 index 00000000..ae406a36 --- /dev/null +++ b/SaltedgeAuthenticatorCore/Classes/Helpers/DeviceModelExtension.swift @@ -0,0 +1,231 @@ +// +// DeviceModelExtension +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import UIKit + +enum DeviceModel: String { + //Simulator + case simulator = "simulator/sandbox", + + //iPad + iPad2 = "iPad 2", + iPad3 = "iPad 3", + iPad4 = "iPad 4", + iPadAir = "iPad Air ", + iPadAir2 = "iPad Air 2", + iPadAir3 = "iPad Air 3", + iPadAir4 = "iPad Air 4", + iPad5 = "iPad 5", //iPad 2017 + iPad6 = "iPad 6", //iPad 2018 + iPad7 = "iPad 7", //iPad 2019 + iPad8 = "iPad 8", //iPad 2020 + + //iPad Mini + iPadMini = "iPad Mini", + iPadMini2 = "iPad Mini 2", + iPadMini3 = "iPad Mini 3", + iPadMini4 = "iPad Mini 4", + iPadMini5 = "iPad Mini 5", + + //iPad Pro + iPadPro9_7 = "iPad Pro 9.7\"", + iPadPro10_5 = "iPad Pro 10.5\"", + iPadPro11 = "iPad Pro 11\"", + iPadPro2_11 = "iPad Pro 11\" 2nd gen", + iPadPro3_11 = "iPad Pro 11\" 3nd gen", + iPadPro12_9 = "iPad Pro 12.9\"", + iPadPro2_12_9 = "iPad Pro 2 12.9\"", + iPadPro3_12_9 = "iPad Pro 3 12.9\"", + iPadPro4_12_9 = "iPad Pro 4 12.9\"", + iPadPro5_12_9 = "iPad Pro 5 12.9\"", + + //iPhone + iPhone4 = "iPhone 4", + iPhone4S = "iPhone 4S", + iPhone5 = "iPhone 5", + iPhone5S = "iPhone 5S", + iPhone5C = "iPhone 5C", + iPhone6 = "iPhone 6", + iPhone6Plus = "iPhone 6 Plus", + iPhone6S = "iPhone 6S", + iPhone6SPlus = "iPhone 6S Plus", + iPhoneSE = "iPhone SE", + iPhone7 = "iPhone 7", + iPhone7Plus = "iPhone 7 Plus", + iPhone8 = "iPhone 8", + iPhone8Plus = "iPhone 8 Plus", + iPhoneX = "iPhone X", + iPhoneXS = "iPhone XS", + iPhoneXSMax = "iPhone XS Max", + iPhoneXR = "iPhone XR", + iPhone11 = "iPhone 11", + iPhone11Pro = "iPhone 11 Pro", + iPhone11ProMax = "iPhone 11 Pro Max", + iPhoneSE2 = "iPhone SE 2nd gen", + iPhone12Mini = "iPhone 12 Mini", + iPhone12 = "iPhone 12", + iPhone12Pro = "iPhone 12 Pro", + iPhone12ProMax = "iPhone 12 Pro Max", + + unrecognized = "?unrecognized?" +} + +// MARK: UIDevice extensions +extension UIDevice { + var type: DeviceModel { + var systemInfo = utsname() + uname(&systemInfo) + let modelCode = withUnsafePointer(to: &systemInfo.machine) { + $0.withMemoryRebound(to: CChar.self, capacity: 1) { + ptr in String.init(validatingUTF8: ptr) + } + } + + let modelMap : [String: DeviceModel] = [ + //Simulator + "i386" : .simulator, + "x86_64" : .simulator, + + //iPad + "iPad2,1" : .iPad2, + "iPad2,2" : .iPad2, + "iPad2,3" : .iPad2, + "iPad2,4" : .iPad2, + "iPad3,1" : .iPad3, + "iPad3,2" : .iPad3, + "iPad3,3" : .iPad3, + "iPad3,4" : .iPad4, + "iPad3,5" : .iPad4, + "iPad3,6" : .iPad4, + "iPad6,11" : .iPad5, //iPad 2017 + "iPad6,12" : .iPad5, + "iPad7,5" : .iPad6, //iPad 2018 + "iPad7,6" : .iPad6, + "iPad7,11" : .iPad7, //iPad 2019 + "iPad7,12" : .iPad7, + "iPad11,6" : .iPad8, //iPad 2020 + "iPad11,7" : .iPad8, + + //iPad Mini + "iPad2,5" : .iPadMini, + "iPad2,6" : .iPadMini, + "iPad2,7" : .iPadMini, + "iPad4,4" : .iPadMini2, + "iPad4,5" : .iPadMini2, + "iPad4,6" : .iPadMini2, + "iPad4,7" : .iPadMini3, + "iPad4,8" : .iPadMini3, + "iPad4,9" : .iPadMini3, + "iPad5,1" : .iPadMini4, + "iPad5,2" : .iPadMini4, + "iPad11,1" : .iPadMini5, + "iPad11,2" : .iPadMini5, + + //iPad Pro + "iPad6,3" : .iPadPro9_7, + "iPad6,4" : .iPadPro9_7, + "iPad7,3" : .iPadPro10_5, + "iPad7,4" : .iPadPro10_5, + "iPad6,7" : .iPadPro12_9, + "iPad6,8" : .iPadPro12_9, + "iPad7,1" : .iPadPro2_12_9, + "iPad7,2" : .iPadPro2_12_9, + "iPad8,1" : .iPadPro11, + "iPad8,2" : .iPadPro11, + "iPad8,3" : .iPadPro11, + "iPad8,4" : .iPadPro11, + "iPad8,9" : .iPadPro2_11, + "iPad8,10" : .iPadPro2_11, + "iPad13,4" : .iPadPro3_11, + "iPad13,5" : .iPadPro3_11, + "iPad13,6" : .iPadPro3_11, + "iPad13,7" : .iPadPro3_11, + "iPad8,5" : .iPadPro3_12_9, + "iPad8,6" : .iPadPro3_12_9, + "iPad8,7" : .iPadPro3_12_9, + "iPad8,8" : .iPadPro3_12_9, + "iPad8,11" : .iPadPro4_12_9, + "iPad8,12" : .iPadPro4_12_9, + "iPad13,8" : .iPadPro5_12_9, + "iPad13,9" : .iPadPro5_12_9, + "iPad13,10" : .iPadPro5_12_9, + "iPad13,11" : .iPadPro5_12_9, + + //iPad Air + "iPad4,1" : .iPadAir, + "iPad4,2" : .iPadAir, + "iPad4,3" : .iPadAir, + "iPad5,3" : .iPadAir2, + "iPad5,4" : .iPadAir2, + "iPad11,3" : .iPadAir3, + "iPad11,4" : .iPadAir3, + "iPad13,1" : .iPadAir4, + "iPad13,2" : .iPadAir4, + + //iPhone + "iPhone3,1" : .iPhone4, + "iPhone3,2" : .iPhone4, + "iPhone3,3" : .iPhone4, + "iPhone4,1" : .iPhone4S, + "iPhone5,1" : .iPhone5, + "iPhone5,2" : .iPhone5, + "iPhone5,3" : .iPhone5C, + "iPhone5,4" : .iPhone5C, + "iPhone6,1" : .iPhone5S, + "iPhone6,2" : .iPhone5S, + "iPhone7,1" : .iPhone6Plus, + "iPhone7,2" : .iPhone6, + "iPhone8,1" : .iPhone6S, + "iPhone8,2" : .iPhone6SPlus, + "iPhone8,4" : .iPhoneSE, + "iPhone9,1" : .iPhone7, + "iPhone9,3" : .iPhone7, + "iPhone9,2" : .iPhone7Plus, + "iPhone9,4" : .iPhone7Plus, + "iPhone10,1" : .iPhone8, + "iPhone10,4" : .iPhone8, + "iPhone10,2" : .iPhone8Plus, + "iPhone10,5" : .iPhone8Plus, + "iPhone10,3" : .iPhoneX, + "iPhone10,6" : .iPhoneX, + "iPhone11,2" : .iPhoneXS, + "iPhone11,4" : .iPhoneXSMax, + "iPhone11,6" : .iPhoneXSMax, + "iPhone11,8" : .iPhoneXR, + "iPhone12,1" : .iPhone11, + "iPhone12,3" : .iPhone11Pro, + "iPhone12,5" : .iPhone11ProMax, + "iPhone12,8" : .iPhoneSE2, + "iPhone13,1" : .iPhone12Mini, + "iPhone13,2" : .iPhone12, + "iPhone13,3" : .iPhone12Pro, + "iPhone13,4" : .iPhone12ProMax + ] + + if let model = modelMap[String.init(validatingUTF8: modelCode!)!] { + return model + } + + return DeviceModel.unrecognized + } +} diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Helpers/ParametersSerializer.swift b/SaltedgeAuthenticatorCore/Classes/Helpers/ParametersSerializer.swift similarity index 90% rename from SaltedgeAuthenticatorSDK/Classes/API/Helpers/ParametersSerializer.swift rename to SaltedgeAuthenticatorCore/Classes/Helpers/ParametersSerializer.swift index e78c2989..a5cdbf6c 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Helpers/ParametersSerializer.swift +++ b/SaltedgeAuthenticatorCore/Classes/Helpers/ParametersSerializer.swift @@ -2,7 +2,7 @@ // ParametersSerializer.swift // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -22,8 +22,8 @@ import Foundation -struct ParametersSerializer { - static func createBody(parameters: [String: Any]) -> Data? { +public struct ParametersSerializer { + public static func createBody(parameters: [String: Any]) -> Data? { do { var data: Data if #available(iOS 11.0, *) { diff --git a/SaltedgeAuthenticatorSDK/Classes/Helpers/SEPollingTimer.swift b/SaltedgeAuthenticatorCore/Classes/Helpers/SEPollingTimer.swift similarity index 96% rename from SaltedgeAuthenticatorSDK/Classes/Helpers/SEPollingTimer.swift rename to SaltedgeAuthenticatorCore/Classes/Helpers/SEPollingTimer.swift index eb9fe1b7..152c20fa 100644 --- a/SaltedgeAuthenticatorSDK/Classes/Helpers/SEPollingTimer.swift +++ b/SaltedgeAuthenticatorCore/Classes/Helpers/SEPollingTimer.swift @@ -1,8 +1,8 @@ // -// SEPoller +// SEPollingTimer // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2020 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -47,3 +47,4 @@ public class SEPoller { timer = nil } } + diff --git a/SaltedgeAuthenticatorSDK/Classes/Helpers/TypeAliases.swift b/SaltedgeAuthenticatorCore/Classes/Helpers/TypeAliases.swift similarity index 77% rename from SaltedgeAuthenticatorSDK/Classes/Helpers/TypeAliases.swift rename to SaltedgeAuthenticatorCore/Classes/Helpers/TypeAliases.swift index 6dae3fd3..d5986cbb 100644 --- a/SaltedgeAuthenticatorSDK/Classes/Helpers/TypeAliases.swift +++ b/SaltedgeAuthenticatorCore/Classes/Helpers/TypeAliases.swift @@ -2,7 +2,7 @@ // TypeAliases.swift // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -20,10 +20,11 @@ // under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md // +public typealias ApiVersion = String + public typealias SuccessBlock = () -> () public typealias FailureBlock = (String) -> () public typealias RequestSuccessBlock = ([String: Any]?) -> () -public typealias HTTPServiceSuccessClosure = (T) -> () public typealias AccessToken = String public typealias GUID = String @@ -32,3 +33,9 @@ public typealias ID = String public typealias PushToken = String public typealias ConnectQuery = String public typealias ApplicationLanguage = String + +public typealias SuccessClosure = () -> () +public typealias SimpleFailureClosure = (String) -> () +public typealias FullFailureClosure = ([String: Any]) -> () +public typealias RequestSuccessClosure = ([String: Any]?) -> () +public typealias SEHTTPResponse = ((T) -> Void)? diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Routers/Routable.swift b/SaltedgeAuthenticatorCore/Classes/Routers/Routable.swift similarity index 67% rename from SaltedgeAuthenticatorSDK/Classes/API/Routers/Routable.swift rename to SaltedgeAuthenticatorCore/Classes/Routers/Routable.swift index ff9d8478..0215197d 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Routers/Routable.swift +++ b/SaltedgeAuthenticatorCore/Classes/Routers/Routable.swift @@ -2,7 +2,7 @@ // Routable.swift // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -22,19 +22,19 @@ import Foundation -enum HTTPMethod: String { +public enum HTTPMethod: String { case get = "GET" case post = "POST" case put = "PUT" case delete = "DELETE" } -enum Encoding: String { +public enum Encoding: String { case url case json } -protocol Routable { +public protocol Routable { var method: HTTPMethod { get } var encoding: Encoding { get } var url: URL { get } @@ -42,11 +42,12 @@ protocol Routable { var parameters: [String: Any]? { get } } -extension Routable { +public extension Routable { func asURLRequest() -> URLRequest { var request = URLRequest(url: url) request.httpMethod = method.rawValue request.allHTTPHeaderFields = headers + request.setValue(userAgentValue, forHTTPHeaderField: "User-Agent") guard let parameters = parameters else { return request } @@ -56,14 +57,27 @@ extension Routable { request.url = components?.url } - if request.value(forHTTPHeaderField: HeadersKeys.contentType) == nil { - request.setValue("application/json", forHTTPHeaderField: HeadersKeys.contentType) + if request.value(forHTTPHeaderField: "Content-Type") == nil { + request.setValue("application/json", forHTTPHeaderField: "Content-Type") } request.httpBody = ParametersSerializer.createBody(parameters: parameters) return request } + + /* + Build application and device info: + e.g.: Authenticator / 3.3.0(130); (iPhone 8 Plus; iOS 14.5) + */ + private var userAgentValue: String { + let appName = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String ?? "" + let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "" + let buildNumber = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String ?? "" + + return "\(appName) / \(version)(\(buildNumber)); " + + "(\(UIDevice().type.rawValue); \(UIDevice.current.systemName) \(UIDevice.current.systemVersion))" + } } private extension Dictionary { diff --git a/SaltedgeAuthenticatorSDK/Classes/SEWebView/SEWebView.swift b/SaltedgeAuthenticatorCore/Classes/SEWebView/SEWebView.swift similarity index 98% rename from SaltedgeAuthenticatorSDK/Classes/SEWebView/SEWebView.swift rename to SaltedgeAuthenticatorCore/Classes/SEWebView/SEWebView.swift index e9fd4111..c07cecda 100644 --- a/SaltedgeAuthenticatorSDK/Classes/SEWebView/SEWebView.swift +++ b/SaltedgeAuthenticatorCore/Classes/SEWebView/SEWebView.swift @@ -2,7 +2,7 @@ // SEWebView.swift // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by diff --git a/SaltedgeAuthenticatorSDK/Classes/Extensions/URLExtensions.swift b/SaltedgeAuthenticatorCore/Classes/URLExtensions.swift similarity index 95% rename from SaltedgeAuthenticatorSDK/Classes/Extensions/URLExtensions.swift rename to SaltedgeAuthenticatorCore/Classes/URLExtensions.swift index 97cde44b..03481413 100644 --- a/SaltedgeAuthenticatorSDK/Classes/Extensions/URLExtensions.swift +++ b/SaltedgeAuthenticatorCore/Classes/URLExtensions.swift @@ -2,7 +2,7 @@ // URLExtensions.swift // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -22,7 +22,7 @@ import Foundation -extension URL { +public extension URL { func queryItem(for key: String) -> String? { guard let components = URLComponents(url: self, resolvingAgainstBaseURL: false) else { return nil } diff --git a/SaltedgeAuthenticatorCore/SaltedgeAuthenticatorCore.podspec b/SaltedgeAuthenticatorCore/SaltedgeAuthenticatorCore.podspec new file mode 100644 index 00000000..e46a2a86 --- /dev/null +++ b/SaltedgeAuthenticatorCore/SaltedgeAuthenticatorCore.podspec @@ -0,0 +1,31 @@ +# +# Be sure to run `pod lib lint SaltedgeAuthenticatorSDK.podspec' to ensure this is a +# valid spec before submitting. +# +# Any lines starting with a # are optional, but their use is encouraged +# To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html +# + +Pod::Spec.new do |s| + s.name = 'SaltedgeAuthenticatorCore' + s.version = '1.1.0' + s.summary = 'SDK for decoupled authentication solution to meet the requirements of Strong Customer Authentication (SCA)' + + s.description = <<-DESC + Authenticator iOS SDK - is a module for connecting to Salt Edge Authenticator API of + Bank (Service Provider) System, that implements Strong Customer Authentication/Dynamic Linking process. + DESC + + s.homepage = 'https://github.com/saltedge/sca-authenticator-ios' + s.license = { :type => 'GPLv3', :file => '../LICENSE.txt' } + s.author = { 'Salt Edge Inc.' => 'authenticator@saltedge.com' } + s.source = { :git => 'https://github.com/saltedge/sca-authenticator-ios.git', :tag => s.version.to_s } + + s.ios.deployment_target = '10.0' + s.swift_version = '5' + s.module_name = 'SEAuthenticatorCore' + + s.source_files = '**/Classes/**/*' + + s.dependency 'CryptoSwift' +end diff --git a/SaltedgeAuthenticatorSDK.podspec b/SaltedgeAuthenticatorSDK.podspec index a49b7694..b5269aa0 100644 --- a/SaltedgeAuthenticatorSDK.podspec +++ b/SaltedgeAuthenticatorSDK.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'SaltedgeAuthenticatorSDK' - s.version = '1.1.1' + s.version = '1.1.0' s.summary = 'SDK for decoupled authentication solution to meet the requirements of Strong Customer Authentication (SCA)' s.description = <<-DESC @@ -27,5 +27,6 @@ Pod::Spec.new do |s| s.source_files = 'SaltedgeAuthenticatorSDK/Classes/**/*' + s.dependency 'SaltedgeAuthenticatorCore' s.dependency 'CryptoSwift' end diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEActionManager.swift b/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEActionManager.swift index 1bff34ee..dad1dead 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEActionManager.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEActionManager.swift @@ -21,16 +21,17 @@ // import Foundation +import SEAuthenticatorCore public struct SEActionManager { public static func submitAction( data: SEActionRequestData, - onSuccess success: @escaping HTTPServiceSuccessClosure, + onSuccess success: SEHTTPResponse, onFailure failure: @escaping FailureBlock ) { - HTTPService.execute( - request: SEActionRouter.submit(data), - success: success, + HTTPService.makeRequest( + SEActionRouter.submit(data), + completion: success, failure: failure ) } diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEAuthorizationManager.swift b/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEAuthorizationManager.swift index 584e37b8..58479ae5 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEAuthorizationManager.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEAuthorizationManager.swift @@ -21,52 +21,53 @@ // import Foundation +import SEAuthenticatorCore public struct SEAuthorizationManager { public static func getEncryptedAuthorizations( data: SEBaseAuthenticatedRequestData, - onSuccess success: @escaping HTTPServiceSuccessClosure, + onSuccess success: SEHTTPResponse, onFailure failure: @escaping FailureBlock ) { - HTTPService.execute( - request: SEAuthorizationRouter.list(data), - success: success, + HTTPService.makeRequest( + SEAuthorizationRouter.list(data), + completion: success, failure: failure ) } public static func getEncryptedAuthorization( data: SEBaseAuthenticatedWithIdRequestData, - onSuccess success: @escaping HTTPServiceSuccessClosure, + onSuccess success: SEHTTPResponse, onFailure failure: @escaping FailureBlock ) { - HTTPService.execute( - request: SEAuthorizationRouter.getAuthorization(data), - success: success, + HTTPService.makeRequest( + SEAuthorizationRouter.getAuthorization(data), + completion: success, failure: failure ) } public static func confirmAuthorization( data: SEConfirmAuthorizationRequestData, - onSuccess success: @escaping HTTPServiceSuccessClosure, + onSuccess success: SEHTTPResponse, onFailure failure: @escaping FailureBlock ) { - HTTPService.execute( - request: SEAuthorizationRouter.confirm(data), - success: success, + HTTPService.makeRequest( + SEAuthorizationRouter.confirm(data), + completion: success, failure: failure ) } public static func denyAuthorization( data: SEConfirmAuthorizationRequestData, - onSuccess success: @escaping HTTPServiceSuccessClosure, + onSuccess success: SEHTTPResponse, onFailure failure: @escaping FailureBlock ) { - HTTPService.execute( - request: SEAuthorizationRouter.deny(data), - success: success, + HTTPService.makeRequest( + SEAuthorizationRouter.deny(data), + completion: success, failure: failure ) } diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEConnectionManager.swift b/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEConnectionManager.swift index 18df2ba5..48b5666a 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEConnectionManager.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEConnectionManager.swift @@ -21,6 +21,7 @@ // import Foundation +import SEAuthenticatorCore public struct SEConnectionManager { public static func createConnection( @@ -29,24 +30,24 @@ public struct SEConnectionManager { pushToken: PushToken, connectQuery: ConnectQuery? = nil, appLanguage: ApplicationLanguage, - onSuccess success: @escaping HTTPServiceSuccessClosure, + onSuccess success: SEHTTPResponse, onFailure failure: @escaping FailureBlock ) { - HTTPService.execute( - request: SEConnectionRouter.createConnection(url, data, pushToken, connectQuery, appLanguage), - success: success, + HTTPService.makeRequest( + SEConnectionRouter.createConnection(url, data, pushToken, connectQuery, appLanguage), + completion: success, failure: failure ) } public static func revokeConnection( data: SEBaseAuthenticatedWithIdRequestData, - onSuccess success: @escaping HTTPServiceSuccessClosure, + onSuccess success: SEHTTPResponse, onFailure failure: @escaping FailureBlock ) { - HTTPService.execute( - request: SEConnectionRouter.revoke(data), - success: success, + HTTPService.makeRequest( + SEConnectionRouter.revoke(data), + completion: success, failure: failure ) } diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEConsentsManager.swift b/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEConsentManager.swift similarity index 72% rename from SaltedgeAuthenticatorSDK/Classes/API/Managers/SEConsentsManager.swift rename to SaltedgeAuthenticatorSDK/Classes/API/Managers/SEConsentManager.swift index 81668194..74226059 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEConsentsManager.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEConsentManager.swift @@ -1,5 +1,5 @@ // -// SEConsentsManager.swift +// SEConsentManager.swift // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) // Copyright © 2020 Salt Edge Inc. @@ -21,28 +21,29 @@ // import Foundation +import SEAuthenticatorCore -public struct SEConsentsManager { +public struct SEConsentManager { public static func getEncryptedConsents( data: SEBaseAuthenticatedRequestData, - onSuccess success: @escaping HTTPServiceSuccessClosure, + onSuccess success: SEHTTPResponse, onFailure failure: @escaping FailureBlock ) { - HTTPService.execute( - request: SEConsentsRouter.list(data), - success: success, + HTTPService.makeRequest( + SEConsentRouter.list(data), + completion: success, failure: failure ) } public static func revokeConsent( data: SEBaseAuthenticatedWithIdRequestData, - onSuccess success: @escaping HTTPServiceSuccessClosure, + onSuccess success: SEHTTPResponse, onFailure failure: @escaping FailureBlock ) { - HTTPService.execute( - request: SEConsentsRouter.revoke(data), - success: success, + HTTPService.makeRequest( + SEConsentRouter.revoke(data), + completion: success, failure: failure ) } diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEProviderManager.swift b/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEProviderManager.swift index 52b16810..bc3fe9e8 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEProviderManager.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Managers/SEProviderManager.swift @@ -21,16 +21,17 @@ // import Foundation +import SEAuthenticatorCore public struct SEProviderManager { public static func fetchProviderData( url: URL, - onSuccess success: @escaping HTTPServiceSuccessClosure, + onSuccess success: SEHTTPResponse, onFailure failure: @escaping FailureBlock ) { - HTTPService.execute( - request: SEProviderRouter.fetchData(url), - success: success, + HTTPService.makeRequest( + SEProviderRouter.fetchData(url), + completion: success, failure: failure ) } diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/SEActionRequestData.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/SEActionRequestData.swift index 1f655ee6..4997f449 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/SEActionRequestData.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/SEActionRequestData.swift @@ -21,6 +21,7 @@ // import Foundation +import SEAuthenticatorCore public class SEActionRequestData: SEBaseAuthenticatedRequestData { public let guid: GUID diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/SECreateConnectionRequestData.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/SECreateConnectionRequestData.swift index ba0e7bc8..2694966f 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/SECreateConnectionRequestData.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/Requests/SECreateConnectionRequestData.swift @@ -21,6 +21,7 @@ // import Foundation +import SEAuthenticatorCore public struct SECreateConnectionRequestData { public let providerCode: String diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/AuthorizationResponses.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/AuthorizationResponses.swift deleted file mode 100644 index 2b6a189f..00000000 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/AuthorizationResponses.swift +++ /dev/null @@ -1,65 +0,0 @@ -// -// AuthorizationResponses.swift -// This file is part of the Salt Edge Authenticator distribution -// (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 3 or later. -// -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -// For the additional permissions granted for Salt Edge Authenticator -// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md -// - -import Foundation - -public struct SEEncryptedDataResponse: SerializableResponse { - public var data: SEEncryptedData - - public init?(_ value: Any) { - if let response = (value as AnyObject)[SENetKeys.data] as? [String: Any], - let data = SEEncryptedData(response) { - self.data = data - } else { - return nil - } - } -} - -public struct SEEncryptedListResponse: SerializableResponse { - public var data: [SEEncryptedData] = [] - - public init?(_ value: Any) { - if let responses = (value as AnyObject)[SENetKeys.data] as? [[String: Any]] { - self.data = responses.compactMap { SEEncryptedData($0) } - } else { - return nil - } - } -} - -public struct SEConfirmAuthorizationResponse: SerializableResponse { - public let id: String - public let success: Bool - - public init?(_ value: Any) { - if let dict = value as? [String: Any], - let data = dict[SENetKeys.data] as? [String: Any], - let success = data[SENetKeys.success] as? Bool, - let id = data[SENetKeys.id] as? String { - self.id = id - self.success = success - } else { - return nil - } - } -} diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SEConfirmAuthorizationResponse.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SEConfirmAuthorizationResponse.swift new file mode 100644 index 00000000..25b0e4e6 --- /dev/null +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SEConfirmAuthorizationResponse.swift @@ -0,0 +1,45 @@ +// +// SEConfirmAuthorizationResponse +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +public struct SEConfirmAuthorizationResponse: Decodable { + public let id: String + public let success: Bool + + enum CodingKeys: String, CodingKey { + case data + } + + enum DataCodingKeys: String, CodingKey { + case id + case success + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let dataContainer = try container.nestedContainer(keyedBy: DataCodingKeys.self, forKey: .data) + id = try dataContainer.decode(String.self, forKey: .id) + success = try dataContainer.decode(Bool.self, forKey: .success) + } +} diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SECreateConnectionResponse.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SECreateConnectionResponse.swift index 2564972d..4b71b75a 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SECreateConnectionResponse.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SECreateConnectionResponse.swift @@ -21,21 +21,28 @@ // import Foundation +import SEAuthenticatorCore -public struct SECreateConnectionResponse: SerializableResponse { +public struct SECreateConnectionResponse: Decodable { public let id: String public let accessToken: String? public let connectUrl: String? // NOTE: Open in SEWebView - public init?(_ value: Any) { - if let dict = value as? [String: Any], - let data = dict[SENetKeys.data] as? [String: Any], - let id = data[SENetKeys.id] as? String { - self.id = id - self.connectUrl = data[SENetKeys.connectUrl] as? String - self.accessToken = data[SENetKeys.accessToken] as? String - } else { - return nil - } + enum CodingKeys: String, CodingKey { + case data + } + + enum DataCodingKeys: String, CodingKey { + case id + case accessToken = "access_token" + case connectUrl = "connect_url" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let dataContainer = try container.nestedContainer(keyedBy: DataCodingKeys.self, forKey: .data) + id = try dataContainer.decode(String.self, forKey: .id) + accessToken = try dataContainer.decodeIfPresent(String.self, forKey: .accessToken) + connectUrl = try dataContainer.decodeIfPresent(String.self, forKey: .connectUrl) } } diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SEProviderResponse.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SEProviderResponse.swift index 0baba929..f0680455 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SEProviderResponse.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SEProviderResponse.swift @@ -21,37 +21,41 @@ // import Foundation +import SEAuthenticatorCore -public struct SEProviderResponse: SerializableResponse { +public struct SEProviderResponse: Decodable { + public var baseUrl: URL public let name: String public let code: String - public let connectUrl: URL - public let version: String + public var version: String public var logoUrl: URL? public var supportEmail: String public let geolocationRequired: Bool? - public init?(_ value: Any) { - if let dict = value as? [String: Any], - let data = dict[SENetKeys.data] as? [String: Any], - let name = data[SENetKeys.name] as? String, - let code = data[SENetKeys.code] as? String, - let connectUrlString = data[SENetKeys.connectUrl] as? String, - let version = data[SENetKeys.version] as? String, - let connectUrl = URL(string: connectUrlString) { + enum CodingKeys: String, CodingKey { + case data + } + + enum DataCodingKeys: String, CodingKey { + case name + case code + case baseUrl = "connect_url" + case logoUrl = "logo_url" + case version + case supportEmail = "support_email" + case geolocationRequired = "geolocation_required" + } - if let logoUrlString = data[SENetKeys.logoUrl] as? String, - let logoUrl = URL(string: logoUrlString) { - self.logoUrl = logoUrl - } - geolocationRequired = data[SENetKeys.geolocationRequired] as? Bool - self.supportEmail = (data[SENetKeys.supportEmail] as? String) ?? "" - self.name = name - self.code = code - self.connectUrl = connectUrl - self.version = version - } else { - return nil - } + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let dataContainer = try container.nestedContainer(keyedBy: DataCodingKeys.self, forKey: .data) + name = try dataContainer.decode(String.self, forKey: .name) + code = try dataContainer.decode(String.self, forKey: .code) + baseUrl = try dataContainer.decode(URL.self, forKey: .baseUrl) + logoUrl = try dataContainer.decodeIfPresent(URL.self, forKey: .logoUrl) + version = try dataContainer.decode(String.self, forKey: .version) + supportEmail = try dataContainer.decode(String.self, forKey: .supportEmail) + version = try dataContainer.decode(String.self, forKey: .version) + geolocationRequired = try dataContainer.decodeIfPresent(Bool.self, forKey: .geolocationRequired) } } diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SERevokeConnectionResponse.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SERevokeConnectionResponse.swift index 4996b0f6..a6886248 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SERevokeConnectionResponse.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SERevokeConnectionResponse.swift @@ -21,17 +21,22 @@ // import Foundation +import SEAuthenticatorCore -public struct SERevokeConnectionResponse: SerializableResponse { +public struct SERevokeConnectionResponse: Decodable { public let success: Bool - public init?(_ value: Any) { - if let dict = value as? [String: Any], - let data = dict[SENetKeys.data] as? [String: Any], - let success = data[SENetKeys.success] as? Bool { - self.success = success - } else { - return nil - } + enum CodingKeys: String, CodingKey { + case data + } + + enum DataCodingKeys: String, CodingKey { + case success + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let dataContainer = try container.nestedContainer(keyedBy: DataCodingKeys.self, forKey: .data) + success = try dataContainer.decode(Bool.self, forKey: .success) } } diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SERevokeConsentResponse.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SERevokeConsentResponse.swift index 67c4c548..dfbafabb 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SERevokeConsentResponse.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SERevokeConsentResponse.swift @@ -21,20 +21,25 @@ // import Foundation +import SEAuthenticatorCore -public struct SERevokeConsentResponse: SerializableResponse { +public struct SERevokeConsentResponse: Decodable { public let consentId: String public let success: Bool - public init?(_ value: Any) { - if let dict = value as? [String: Any], - let data = dict[SENetKeys.data] as? [String: Any], - let success = data[SENetKeys.success] as? Bool, - let consentId = data[SENetKeys.consentId] as? String { - self.consentId = consentId - self.success = success - } else { - return nil - } + enum CodingKeys: String, CodingKey { + case data + } + + enum DataCodingKeys: String, CodingKey { + case success + case consentId = "consent_id" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let dataContainer = try container.nestedContainer(keyedBy: DataCodingKeys.self, forKey: .data) + success = try dataContainer.decode(Bool.self, forKey: .success) + consentId = try dataContainer.decode(String.self, forKey: .consentId) } } diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SESubmitActionResponse.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SESubmitActionResponse.swift index c48c9d6f..c7bc92b5 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SESubmitActionResponse.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/Responses/SESubmitActionResponse.swift @@ -21,25 +21,28 @@ // import Foundation +import SEAuthenticatorCore -public struct SESubmitActionResponse: SerializableResponse { +public struct SESubmitActionResponse: SEBaseActionResponse { public let success: Bool public var authorizationId: String? public var connectionId: String? - public init?(_ value: Any) { - if let dict = value as? [String: Any], - let data = dict[SENetKeys.data] as? [String: Any], - let success = data[SENetKeys.success] as? Bool { - if let authorizationId = data[SENetKeys.authorizationId] as? String { - self.authorizationId = authorizationId - } - if let connectionId = data[SENetKeys.connectionId] as? String { - self.connectionId = connectionId - } - self.success = success - } else { - return nil - } + enum CodingKeys: String, CodingKey { + case data + } + + enum DataCodingKeys: String, CodingKey { + case success + case authorizationId = "authorization_id" + case connectionId = "connection_id" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let dataContainer = try container.nestedContainer(keyedBy: DataCodingKeys.self, forKey: .data) + success = try dataContainer.decode(Bool.self, forKey: .success) + authorizationId = try dataContainer.decodeIfPresent(String.self, forKey: .authorizationId) + connectionId = try dataContainer.decodeIfPresent(String.self, forKey: .connectionId) } } diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/SEAuthorizationData.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/SEAuthorizationData.swift index e497a7ce..4407ae1c 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/SEAuthorizationData.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/SEAuthorizationData.swift @@ -21,17 +21,20 @@ // import Foundation +import SEAuthenticatorCore -public struct SEAuthorizationData { - public let id: String +public class SEAuthorizationData: SEBaseAuthorizationData { + public var id: String public let connectionId: String public let title: String public let description: String - public let createdAt: Date - public let expiresAt: Date + public var createdAt: Date + public var expiresAt: Date public var authorizationCode: String? + public var apiVersion: ApiVersion = "1" public init?(_ dictionary: [String: Any]) { + print("$$$$$ \(dictionary)") if let id = dictionary[SENetKeys.id] as? String, let connectionId = dictionary[SENetKeys.connectionId] as? String, let title = dictionary[SENetKeys.title] as? String, diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Networking.swift b/SaltedgeAuthenticatorSDK/Classes/API/Networking.swift deleted file mode 100644 index b4b99916..00000000 --- a/SaltedgeAuthenticatorSDK/Classes/API/Networking.swift +++ /dev/null @@ -1,75 +0,0 @@ -// -// Networking.swift -// This file is part of the Salt Edge Authenticator distribution -// (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 3 or later. -// -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -// For the additional permissions granted for Salt Edge Authenticator -// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md -// - -import Foundation - -extension Networking { - static func execute(_ request: Routable, - success: @escaping RequestSuccessBlock, - failure: @escaping FailureBlock) { - let task = URLSessionManager.shared.dataTask(with: request.asURLRequest()) { data, response, error in - if let error = error { - DispatchQueue.main.async { failure(error.localizedDescription) } - } else { - guard let response = response as? HTTPURLResponse else { - DispatchQueue.main.async { failure("Something went wrong") } - return - } - - if (200...299).contains(response.statusCode) { - guard let jsonData = deserializedDictionary(from: data) else { - return DispatchQueue.main.async { failure("Something went wrong") } - } - - DispatchQueue.main.async { success(jsonData) } - } else { - guard let jsonData = deserializedDictionary(from: data) else { - return DispatchQueue.main.async { - failure("Request not successful. HTTP status code: \(response.statusCode), \(response.description)") - } - } - - if jsonData[SENetKeys.errorMessage] as? String != nil, - let errorClass = jsonData[SENetKeys.errorClass] as? String { - DispatchQueue.main.async { failure(errorClass) } - } else { - DispatchQueue.main.async { - failure("Request not successful. HTTP status code: \(response.statusCode), \(response.description)") - } - } - } - } - } - - task.resume() - } - - private static func deserializedDictionary(from data: Data?) -> [String: Any]? { - guard let requestData = data, - let jsonData = try? JSONSerialization.jsonObject( - with: requestData, - options: [] - ) as? [String: Any] else { return nil } - - return jsonData - } -} diff --git a/SaltedgeAuthenticatorSDK/Classes/API/RequestParametersBuilder.swift b/SaltedgeAuthenticatorSDK/Classes/API/RequestParametersBuilder.swift index 917cc7dc..413b93fc 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/RequestParametersBuilder.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/RequestParametersBuilder.swift @@ -21,6 +21,7 @@ // import Foundation +import SEAuthenticatorCore struct ParametersKeys { static let data = "data" diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEActionRouter.swift b/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEActionRouter.swift index e047b6ac..2f503cf8 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEActionRouter.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEActionRouter.swift @@ -21,6 +21,7 @@ // import Foundation +import SEAuthenticatorCore enum SEActionRouter: Routable { case submit(SEActionRequestData) @@ -36,7 +37,7 @@ enum SEActionRouter: Routable { var url: URL { switch self { case .submit(let data): - return data.url.appendingPathComponent("\(SENetPaths.actions.path)/\(data.guid)") + return data.url.appendingPathComponent("\(SENetPathBuilder(for: .actions).path)/\(data.guid)") } } @@ -47,7 +48,9 @@ enum SEActionRouter: Routable { let signature = SignatureHelper.signedPayload( method: .put, - urlString: data.url.appendingPathComponent("\(SENetPaths.actions.path)/\(data.guid)").absoluteString, + urlString: data.url.appendingPathComponent( + "\(SENetPathBuilder(for: .actions).path)/\(data.guid)" + ).absoluteString, guid: data.connectionGuid, expiresAt: expiresAt, params: parameters diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEAuthorizationRouter.swift b/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEAuthorizationRouter.swift index c977f1f9..f4da208c 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEAuthorizationRouter.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEAuthorizationRouter.swift @@ -21,6 +21,7 @@ // import Foundation +import SEAuthenticatorCore enum SEAuthorizationRouter: Routable { case list(SEBaseAuthenticatedRequestData) @@ -45,11 +46,11 @@ enum SEAuthorizationRouter: Routable { var url: URL { switch self { case .list(let data): - return data.url.appendingPathComponent(SENetPaths.authorizations.path) + return data.url.appendingPathComponent(SENetPathBuilder(for: .authorizations).path) case .getAuthorization(let data): - return data.url.appendingPathComponent("\(SENetPaths.authorizations.path)/\(data.entityId)") + return data.url.appendingPathComponent("\(SENetPathBuilder(for: .authorizations).path)/\(data.entityId)") case .confirm(let data), .deny(let data): - return data.url.appendingPathComponent("\(SENetPaths.authorizations.path)/\(data.entityId)") + return data.url.appendingPathComponent("\(SENetPathBuilder(for: .authorizations).path)/\(data.entityId)") } } @@ -78,7 +79,7 @@ enum SEAuthorizationRouter: Routable { let signature = SignatureHelper.signedPayload( method: .get, urlString: data.url.appendingPathComponent( - "\(SENetPaths.authorizations.path)/\(data.entityId)" + "\(SENetPathBuilder(for: .authorizations).path)/\(data.entityId)" ).absoluteString, guid: data.connectionGuid, expiresAt: expiresAt, @@ -97,7 +98,7 @@ enum SEAuthorizationRouter: Routable { let signature = SignatureHelper.signedPayload( method: .put, urlString: data.url.appendingPathComponent( - "\(SENetPaths.authorizations.path)/\(data.entityId)" + "\(SENetPathBuilder(for: .authorizations).path)/\(data.entityId)" ).absoluteString, guid: data.connectionGuid, expiresAt: expiresAt, diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEConnectionRouter.swift b/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEConnectionRouter.swift index a8b12e36..e8f64cb5 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEConnectionRouter.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEConnectionRouter.swift @@ -21,6 +21,7 @@ // import Foundation +import SEAuthenticatorCore enum SEConnectionRouter: Routable { case createConnection(URL, SECreateConnectionRequestData, PushToken, ConnectQuery?, ApplicationLanguage) @@ -42,8 +43,10 @@ enum SEConnectionRouter: Routable { var url: URL { switch self { - case .createConnection(let url, _, _, _, _): return url - case .revoke(let data): return data.url.appendingPathComponent("\(SENetPaths.connections.path)") + case .createConnection(let url, _, _, _, _): + return url.appendingPathComponent(SENetPathBuilder(for: .connections).path) + case .revoke(let data): + return data.url.appendingPathComponent(SENetPathBuilder(for: .connections).path) } } diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEConsentsRouter.swift b/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEConsentRouter.swift similarity index 90% rename from SaltedgeAuthenticatorSDK/Classes/API/Routers/SEConsentsRouter.swift rename to SaltedgeAuthenticatorSDK/Classes/API/Routers/SEConsentRouter.swift index 207545bf..82d4491f 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEConsentsRouter.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEConsentRouter.swift @@ -1,5 +1,5 @@ // -// SEConsentsRouter.swift +// SEConsentRouter.swift // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) // Copyright © 2020 Salt Edge Inc. @@ -21,8 +21,9 @@ // import Foundation +import SEAuthenticatorCore -enum SEConsentsRouter: Routable { +enum SEConsentRouter: Routable { case list(SEBaseAuthenticatedRequestData) case revoke(SEBaseAuthenticatedWithIdRequestData) @@ -42,9 +43,9 @@ enum SEConsentsRouter: Routable { var url: URL { switch self { case .list(let data): - return data.url.appendingPathComponent(SENetPaths.consents.path) + return data.url.appendingPathComponent(SENetPathBuilder(for: .consents).path) case .revoke(let data): - return data.url.appendingPathComponent("\(SENetPaths.consents.path)/\(data.entityId)") + return data.url.appendingPathComponent("\(SENetPathBuilder(for: .consents).path)/\(data.entityId)") } } diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEProviderRouter.swift b/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEProviderRouter.swift index 051014aa..5cdf8fb3 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEProviderRouter.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEProviderRouter.swift @@ -21,6 +21,7 @@ // import Foundation +import SEAuthenticatorCore enum SEProviderRouter: Routable { case fetchData(URL) diff --git a/SaltedgeAuthenticatorSDK/Classes/Helpers/SignatureHelper.swift b/SaltedgeAuthenticatorSDK/Classes/Helpers/SignatureHelper.swift index 53069271..f69a1ff1 100644 --- a/SaltedgeAuthenticatorSDK/Classes/Helpers/SignatureHelper.swift +++ b/SaltedgeAuthenticatorSDK/Classes/Helpers/SignatureHelper.swift @@ -22,13 +22,16 @@ import Foundation import CommonCrypto +import SEAuthenticatorCore struct SignatureHelper { - static func signedPayload(method: HTTPMethod, - urlString: String, - guid: GUID, - expiresAt: Int, - params: [String: Any]?) -> String? { + static func signedPayload( + method: HTTPMethod, + urlString: String, + guid: GUID, + expiresAt: Int, + params: [String: Any]? + ) -> String? { let bodyString: String if let payloadParams = params, let payloadBody = ParametersSerializer.createBody(parameters: payloadParams) { bodyString = String(data: payloadBody, encoding: .utf8) ?? "" diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Headers.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Headers.swift new file mode 100644 index 00000000..cc39b8a4 --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Headers.swift @@ -0,0 +1,72 @@ +// +// Headers +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation + +struct HeadersKeys { + static let accessToken = "Access-Token" + static let accept = "Accept" + static let acceptLanguage = "Accept-Language" + static let contentType = "Content-Type" + static let expiresAt = "Expires-At" + static let signature = "Signature" + static let geolocation = "GEO-Location" + static let authorizationType = "Authorization-Type" + static let jwsSignature = "x-jws-signature" +} + +public struct Headers { + public static func signedRequestHeaders(token: String, payloadParams: [String: Any]?, connectionGuid: String) -> [String: String] { + guard let jwsSignature = JWSHelper.sign(params: payloadParams, guid: connectionGuid) else { return [:] } + + return authorizedRequestHeaders(token: token, appLanguage: "en").merge( + with: [ + HeadersKeys.jwsSignature: jwsSignature + ] + ) + } + + public static func authorizedRequestHeaders(token: String, appLanguage: String = "en") -> [String: String] { + print("REQUEST HEADERS: \(requestHeaders(with: appLanguage).merge(with: [HeadersKeys.accessToken: token]))") + return requestHeaders(with: appLanguage).merge(with: [HeadersKeys.accessToken: token]) + } + + public static func requestHeaders(with appLanguage: String) -> [String: String] { + return [ + HeadersKeys.accept: "application/json", + HeadersKeys.acceptLanguage: appLanguage, + HeadersKeys.contentType: "application/json" + ] + } +} + +extension Dictionary where Key == String, Value == String { + func addLocationHeader(geolocation: String?) -> [String: String] { + guard let geolocation = geolocation else { return self } + + return self.merge(with: [ HeadersKeys.geolocation: geolocation ]) + } + + func addAuthorizationTypeHeader(authorizationType: String) -> [String: String] { + return self.merge(with: [ HeadersKeys.authorizationType: authorizationType ]) + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Helpers/ApiConstants.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Helpers/ApiConstants.swift new file mode 100644 index 00000000..370707f6 --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Helpers/ApiConstants.swift @@ -0,0 +1,40 @@ +// +// ApiConstants +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation + +struct ApiConstants { + static let scaServiceUrl = "sca_service_url" + static let apiVersion = "api_version" + static let providerId = "provider_id" + static let providerName = "provider_name" + static let providerLogoUrl = "provider_logo_url" + static let providerSupportEmail = "provider_support_email" + static let providerPublicKey = "provider_public_key" + + static let authenticationUrl = "authentication_url" + static let userAuthorizationType = "user_authorization_type" + static let geolocation = "geolocation" + + static let actionId = "action_id" + static let connectionId = "connection_id" +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEActionManagerV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEActionManagerV2.swift new file mode 100644 index 00000000..b3985d1f --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEActionManagerV2.swift @@ -0,0 +1,38 @@ +// +// SEActionManagerV2 +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +public struct SEActionManagerV2 { + public static func submitAction( + data: SEActionRequestDataV2, + onSuccess success: SEHTTPResponse, + onFailure failure: @escaping FailureBlock + ) { + HTTPService.makeRequest( + SEActionRouterV2.submit(data), + completion: success, + failure: failure + ) + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEAuthorizationManagerV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEAuthorizationManagerV2.swift new file mode 100644 index 00000000..64797183 --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEAuthorizationManagerV2.swift @@ -0,0 +1,95 @@ +// +// SEAuthorizationManager +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +public struct SEAuthorizationManagerV2 { + public static func getEncryptedAuthorizations( + data: SEBaseAuthenticatedRequestData, + onSuccess success: SEHTTPResponse, + onFailure failure: @escaping FailureBlock + ) { + HTTPService.makeRequest( + SEAuthorizationRouter.list(data), + completion: success, + failure: failure + ) + } + + public static func getEncryptedAuthorization( + data: SEBaseAuthenticatedWithIdRequestData, + onSuccess success: SEHTTPResponse, + onFailure failure: @escaping FailureBlock + ) { + HTTPService.makeRequest( + SEAuthorizationRouter.show(data), + completion: success, + failure: failure + ) + } + + public static func confirmAuthorization( + data: SEConfirmAuthorizationRequestData, + onSuccess success: SEHTTPResponse, + onFailure failure: @escaping FailureBlock + ) { + let parameters = RequestParametersBuilder.confirmAuthorizationParams( + encryptedData: encryptedData(requestData: data), + exp: Date().addingTimeInterval(5.0 * 60.0).utcSeconds + ) + + HTTPService.makeRequest( + SEAuthorizationRouter.confirm(data, parameters), + completion: success, + failure: failure + ) + } + + public static func denyAuthorization( + data: SEConfirmAuthorizationRequestData, + onSuccess success: SEHTTPResponse, + onFailure failure: @escaping FailureBlock + ) { + let parameters = RequestParametersBuilder.confirmAuthorizationParams( + encryptedData: encryptedData(requestData: data), + exp: Date().addingTimeInterval(5.0 * 60.0).utcSeconds + ) + + HTTPService.makeRequest( + SEAuthorizationRouter.deny(data, parameters), + completion: success, + failure: failure + ) + } + + // Encrypt the confirmation payload with connection's provider public key + private static func encryptedData(requestData: SEConfirmAuthorizationRequestData) -> SEEncryptedData? { + guard let data = [ + SENetKeys.authorizationCode: requestData.authorizationCode, + ApiConstants.userAuthorizationType: requestData.authorizationType, + ApiConstants.geolocation: requestData.geolocation + ].jsonString else { return nil } + + return try? SECryptoHelper.encrypt(data, tag: "\(requestData.connectionGuid)_provider_public_key") + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConnectionManagerV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConnectionManagerV2.swift new file mode 100644 index 00000000..c9ef47ec --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConnectionManagerV2.swift @@ -0,0 +1,52 @@ +// +// SEConnectionManagerV2 +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +public struct SEConnectionManagerV2 { + public static func createConnection( + by url: URL, + params: SECreateConnectionParams, + appLanguage: ApplicationLanguage, + onSuccess success: SEHTTPResponse, + onFailure failure: @escaping FailureBlock + ) { + HTTPService.makeRequest( + SEConnectionRouter.createConnection(url, params, appLanguage), + completion: success, + failure: failure + ) + } + + public static func revokeConnection( + data: SEBaseAuthenticatedWithIdRequestData, + onSuccess success: SEHTTPResponse, + onFailure failure: @escaping FailureBlock + ) { + HTTPService.makeRequest( + SEConnectionRouter.revoke(data), + completion: success, + failure: failure + ) + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConsentManagerV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConsentManagerV2.swift new file mode 100644 index 00000000..2e163ceb --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConsentManagerV2.swift @@ -0,0 +1,52 @@ +// +// SEConsentManagerV2 +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +public struct SEConsentManagerV2 { + public static func getEncryptedConsents( + data: SEBaseAuthenticatedRequestData, + onSuccess success: SEHTTPResponse, + onFailure failure: @escaping FailureBlock + ) { + HTTPService.makeRequest( + SEConsentRouter.list(data), + completion: success, + failure: failure + ) + } + + public static func revokeConsent( + data: SEBaseAuthenticatedWithIdRequestData, + onSuccess success: SEHTTPResponse, + onFailure failure: @escaping FailureBlock + ) { + let parameters = RequestParametersBuilder.expirationTimeParameters + + HTTPService.makeRequest( + SEConsentRouter.revoke(data, parameters), + completion: success, + failure: failure + ) + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEProviderManagerV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEProviderManagerV2.swift new file mode 100644 index 00000000..b6618ae9 --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEProviderManagerV2.swift @@ -0,0 +1,38 @@ +// +// SEProviderManagerV2 +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +public struct SEProviderManagerV2 { + public static func fetchProviderData( + url: URL, + onSuccess success: SEHTTPResponse, + onFailure failure: @escaping FailureBlock + ) { + HTTPService.makeRequest( + SEProviderRouterV2.fetchData(url), + completion: success, + failure: failure + ) + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEActionRequestDataV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEActionRequestDataV2.swift new file mode 100644 index 00000000..bf5d7621 --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEActionRequestDataV2.swift @@ -0,0 +1,50 @@ +// +// SEActionRequestDataV2 +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +public class SEActionRequestDataV2: SEBaseAuthenticatedRequestData { + public let providerId: String + public let actionId: String + public let connectionId: String + + public init( + url: URL, + connectionGuid: GUID, + accessToken: AccessToken, + appLanguage: ApplicationLanguage, + providerId: String, + actionId: String, + connectionId: String + ) { + self.providerId = providerId + self.actionId = actionId + self.connectionId = connectionId + super.init( + url: url, + connectionGuid: connectionGuid, + accessToken: accessToken, + appLanguage: appLanguage + ) + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEAuthorizationDataV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEAuthorizationDataV2.swift new file mode 100644 index 00000000..db4d4740 --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEAuthorizationDataV2.swift @@ -0,0 +1,78 @@ +// +// SEAuthorizationDataV2 +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +public class SEAuthorizationDataV2: SEBaseAuthorizationData { + public var title: String = "" + public var description: [String: Any] = [:] + public var createdAt: Date = Date() + public var expiresAt: Date = Date() + public var authorizationCode: String? + + public var id: String + public var connectionId: String + public var status: AuthorizationStatus + + public var apiVersion: ApiVersion = "2" + + public init?(_ dictionary: [String: Any], id: String, connectionId: String, status: AuthorizationStatus) { + if let title = dictionary[SENetKeys.title] as? String, + let description = dictionary[SENetKeys.description] as? [String: Any], + let createdAt = (dictionary[SENetKeys.createdAt] as? String)?.iso8601date, + let expiresAt = (dictionary[SENetKeys.expiresAt] as? String)?.iso8601date, + let authorizationCode = dictionary[SENetKeys.authorizationCode] as? String { + self.authorizationCode = authorizationCode + self.title = title + self.description = description + self.createdAt = createdAt + self.expiresAt = expiresAt + self.id = id + self.connectionId = connectionId + self.status = status + } else { + return nil + } + } + + public init(id: String, connectionId: String, status: AuthorizationStatus) { + self.id = id + self.connectionId = connectionId + self.status = status + } +} + +extension SEAuthorizationDataV2: Equatable { + public static func == (lhs: SEAuthorizationDataV2, rhs: SEAuthorizationDataV2) -> Bool { + return lhs.title == rhs.title && + lhs.description == rhs.description && + lhs.createdAt.withoutTime == rhs.createdAt.withoutTime && + lhs.expiresAt.withoutTime == rhs.expiresAt.withoutTime && + lhs.authorizationCode == rhs.authorizationCode && + lhs.status.rawValue == rhs.status.rawValue + } +} + +private func ==(lhs: [String: Any], rhs: [String: Any]) -> Bool { + return NSDictionary(dictionary: lhs).isEqual(to: rhs) +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEAuthorizationRequestData.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEAuthorizationRequestData.swift new file mode 100644 index 00000000..f20b97df --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Models/SEAuthorizationRequestData.swift @@ -0,0 +1,42 @@ +// +// SEAuthorizationRequestData +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +public struct SEAuthorizationRequestData { + public let id: ID + public let authorizationCode: String + public let userAuthorizationType: String + public let geolocation: String + public let connectionGuid: GUID + + public var encryptedData: SEEncryptedData? { + guard let data = [ + SENetKeys.authorizationCode: authorizationCode, + ApiConstants.userAuthorizationType: userAuthorizationType, + ApiConstants.geolocation: geolocation + ].jsonString else { return nil } + + return try? SECryptoHelper.encrypt(data, tag: SETagHelper.create(for: connectionGuid)) + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/AuthorizationResponses.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/AuthorizationResponses.swift new file mode 100644 index 00000000..ef766ff3 --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/AuthorizationResponses.swift @@ -0,0 +1,50 @@ +// +// AuthorizationResponses +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +public struct SEEncryptedAuthorizationDataResponse: Decodable { + public var data: SEEncryptedAuthorizationData + + enum CodingKeys: String, CodingKey { + case data + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + data = try container.decode(SEEncryptedAuthorizationData.self, forKey: .data) + } +} + +public struct SEEncryptedAuthorizationsListResponse: Decodable { + public var data: [SEEncryptedAuthorizationData] = [] + + enum CodingKeys: String, CodingKey { + case data + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + data = try container.decode([SEEncryptedAuthorizationData].self, forKey: .data) + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEConfirmAuthorizationResponseV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEConfirmAuthorizationResponseV2.swift new file mode 100644 index 00000000..3e21a68e --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEConfirmAuthorizationResponseV2.swift @@ -0,0 +1,46 @@ +// +// SEConfirmAuthorizationResponseV2 +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +public struct SEConfirmAuthorizationResponseV2: Decodable { + public let id: String + public let status: AuthorizationStatus + + enum CodingKeys: String, CodingKey { + case data + } + + enum DataCodingKeys: String, CodingKey { + case id + case status + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let dataContainer = try container.nestedContainer(keyedBy: DataCodingKeys.self, forKey: .data) + let authorizationId = try dataContainer.decode(Int.self, forKey: .id) + id = "\(authorizationId)" + status = try dataContainer.decode(AuthorizationStatus.self, forKey: .status) + } +} diff --git a/SaltedgeAuthenticatorSDK/Classes/API/HTTPService.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SECreateConnectionResponse.swift similarity index 52% rename from SaltedgeAuthenticatorSDK/Classes/API/HTTPService.swift rename to SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SECreateConnectionResponse.swift index 046a1250..98b223c1 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/HTTPService.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SECreateConnectionResponse.swift @@ -1,8 +1,8 @@ // -// HTTPService.swift +// SECreateConnectionResponse // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) -// Copyright © 2019 Salt Edge Inc. +// Copyright © 2021 Salt Edge Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -21,29 +21,26 @@ // import Foundation +import SEAuthenticatorCore -struct HTTPService { - static func execute(request: Routable, - success: @escaping HTTPServiceSuccessClosure, - failure: @escaping FailureBlock) { - BaseNetworking.execute( - request, - success: { response in - if let data = T(response ?? [:]) { - success(data) - } else { - failure(handleErrorResponse(response)) - } - }, - failure: failure - ) +public struct SECreateConnectionResponse: Decodable { + public let id: String + public let authenticationUrl: String + + enum CodingKeys: String, CodingKey { + case data } - private static func handleErrorResponse(_ response: [String: Any]?) -> String { - if let errorMessage = response?[SENetKeys.message] as? String { - return errorMessage - } + enum DataCodingKeys: String, CodingKey { + case id = "connection_id" + case authenticationUrl = "authentication_url" + } - return "Something went wrong" + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let dataContainer = try container.nestedContainer(keyedBy: DataCodingKeys.self, forKey: .data) + let connectionId = try dataContainer.decode(Int.self, forKey: .id) + id = "\(connectionId)" + authenticationUrl = try dataContainer.decode(String.self, forKey: .authenticationUrl) } } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponseV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponseV2.swift new file mode 100644 index 00000000..1e0a24f1 --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponseV2.swift @@ -0,0 +1,64 @@ +// +// SEProviderResponseV2 +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +public struct SEProviderResponseV2: Decodable { + public let name: String + public var baseUrl: URL + public let providerId: String + public let apiVersion: String + public var logoUrl: URL? + public var supportEmail: String + public var publicKey: String + public let geolocationRequired: Bool? + + enum CodingKeys: String, CodingKey { + case data + } + + enum DataCodingKeys: String, CodingKey { + case name = "provider_name" + case baseUrl = "sca_service_url" + case logoUrl = "logo_url" + case apiVersion = "api_version" + case supportEmail = "provider_support_email" + case providerId = "provider_id" + case publicKey = "provider_public_key" + case geolocationRequired = "geolocation_required" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let dataContainer = try container.nestedContainer(keyedBy: DataCodingKeys.self, forKey: .data) + name = try dataContainer.decode(String.self, forKey: .name) + baseUrl = try dataContainer.decode(URL.self, forKey: .baseUrl) + logoUrl = try dataContainer.decodeIfPresent(URL.self, forKey: .logoUrl) + apiVersion = try dataContainer.decode(String.self, forKey: .apiVersion) + supportEmail = try dataContainer.decode(String.self, forKey: .supportEmail) + let id = try dataContainer.decode(Int.self, forKey: .providerId) + providerId = "\(id)" + publicKey = try dataContainer.decode(String.self, forKey: .publicKey) + geolocationRequired = try dataContainer.decodeIfPresent(Bool.self, forKey: .geolocationRequired) + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConnectionResponse.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConnectionResponse.swift new file mode 100644 index 00000000..cefe761b --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConnectionResponse.swift @@ -0,0 +1,43 @@ +// +// SERevokeConnectionResponse +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +public struct SERevokeConnectionResponse: Decodable { + public let connectionId: String + + enum CodingKeys: String, CodingKey { + case data + } + + enum DataCodingKeys: String, CodingKey { + case connectionId = "connection_id" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let dataContainer = try container.nestedContainer(keyedBy: DataCodingKeys.self, forKey: .data) + let id = try dataContainer.decode(Int.self, forKey: .connectionId) + connectionId = "\(id)" + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConsentResponseV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConsentResponseV2.swift new file mode 100644 index 00000000..f9fc65dc --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConsentResponseV2.swift @@ -0,0 +1,43 @@ +// +// SERevokeConsentResponseV2 +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +public struct SERevokeConsentResponseV2: Decodable { + public let id: String + + enum CodingKeys: String, CodingKey { + case data + } + + enum DataCodingKeys: String, CodingKey { + case id + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let dataContainer = try container.nestedContainer(keyedBy: DataCodingKeys.self, forKey: .data) + let consentId = try dataContainer.decode(Int.self, forKey: .id) + id = "\(consentId)" + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SESubmitActionResponseV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SESubmitActionResponseV2.swift new file mode 100644 index 00000000..9fb33284 --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SESubmitActionResponseV2.swift @@ -0,0 +1,47 @@ +// +// SESubmitActionResponseV2 +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +public struct SESubmitActionResponseV2: SEBaseActionResponse { + public var authorizationId: String? + public var connectionId: String? + + enum CodingKeys: String, CodingKey { + case data + } + + enum DataCodingKeys: String, CodingKey { + case authorizationId = "authorization_id" + case connectionId = "connection_id" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let dataContainer = try container.nestedContainer(keyedBy: DataCodingKeys.self, forKey: .data) + let authorizationV2Id = try dataContainer.decodeIfPresent(Int.self, forKey: .authorizationId) + authorizationId = "\(authorizationV2Id)" + let connectionV2Id = try dataContainer.decodeIfPresent(Int.self, forKey: .connectionId) + connectionId = "\(connectionV2Id)" + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEActionRouterV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEActionRouterV2.swift new file mode 100644 index 00000000..15bf99e7 --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEActionRouterV2.swift @@ -0,0 +1,61 @@ +// +// SEActionRouterV2 +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +enum SEActionRouterV2: Routable { + case submit(SEActionRequestDataV2) + + var method: HTTPMethod { + return .post + } + + var encoding: Encoding { + return .json + } + + var url: URL { + switch self { + case .submit(let data): + return data.url.appendingPathComponent(SENetPathBuilder(for: .authorizations, version: 2).path) + } + } + + var headers: [String : String]? { + switch self { + case .submit(let data): + return Headers.signedRequestHeaders( + token: data.accessToken, + payloadParams: RequestParametersBuilder.actionParameters(requestData: data), + connectionGuid: data.connectionGuid + ) + } + } + + var parameters: [String : Any]? { + switch self { + case .submit(let data): + return RequestParametersBuilder.actionParameters(requestData: data) + } + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEAuthorizationRouter.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEAuthorizationRouter.swift new file mode 100644 index 00000000..75d251ff --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEAuthorizationRouter.swift @@ -0,0 +1,88 @@ +// +// SEAuthorizationRouter +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +enum SEAuthorizationRouter: Routable { + case list(SEBaseAuthenticatedRequestData) + case show(SEBaseAuthenticatedWithIdRequestData) + case confirm(SEConfirmAuthorizationRequestData, [String: Any]) + case deny(SEConfirmAuthorizationRequestData, [String: Any]) + + var method: HTTPMethod { + switch self { + case .list, .show: return .get + case .confirm, .deny: return .put + } + } + + var encoding: Encoding { + switch self { + case .list, .show: return .url + case .confirm, .deny: return .json + } + } + + var url: URL { + switch self { + case .list(let data): + return data.url.appendingPathComponent( + "\(SENetPathBuilder(for: .authorizations, version: 2).path)" + ) + case .show(let data): + return data.url.appendingPathComponent( + "\(SENetPathBuilder(for: .authorizations, version: 2).path)/\(data.entityId)" + ) + case .confirm(let data, _): + return data.url.appendingPathComponent( + "\(SENetPathBuilder(for: .authorizations, version: 2).path)/\(data.entityId)/confirm" + ) + case .deny(let data, _): + return data.url.appendingPathComponent( + "\(SENetPathBuilder(for: .authorizations, version: 2).path)/\(data.entityId)/deny" + ) + } + } + + var headers: [String : String]? { + switch self { + case .list(let data): + return Headers.authorizedRequestHeaders(token: data.accessToken) + case .show(let data): + return Headers.authorizedRequestHeaders(token: data.accessToken) + case .confirm(let data, let encryptedParameters), .deny(let data, let encryptedParameters): + return Headers.signedRequestHeaders( + token: data.accessToken, + payloadParams: encryptedParameters, + connectionGuid: data.connectionGuid + ) + } + } + + var parameters: [String : Any]? { + switch self { + case .list, .show: return nil + case .confirm(_, let encryptedParameters), .deny(_, let encryptedParameters): return encryptedParameters + } + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConnectionRouter.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConnectionRouter.swift new file mode 100644 index 00000000..92b56114 --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConnectionRouter.swift @@ -0,0 +1,92 @@ +// +// SEConnectionRouter.swift +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +public struct SECreateConnectionParams { + public let providerId: String + public let pushToken: String? + public let connectQuery: String? + public let encryptedRsaPublicKey: SEEncryptedData + + public init(providerId: String, pushToken: String?, connectQuery: String?, encryptedRsaPublicKey: SEEncryptedData) { + self.providerId = providerId + self.pushToken = pushToken + self.connectQuery = connectQuery + self.encryptedRsaPublicKey = encryptedRsaPublicKey + } +} + +enum SEConnectionRouter: Routable { + case createConnection(URL, SECreateConnectionParams, String) + case revoke(SEBaseAuthenticatedWithIdRequestData) + + var method: HTTPMethod { + switch self { + case .createConnection: return .post + case .revoke: return .put + } + } + + var encoding: Encoding { + switch self { + case .createConnection, .revoke: return .json + } + } + + var url: URL { + switch self { + case .createConnection(let connectUrl, _, _): + return connectUrl.appendingPathComponent(SENetPathBuilder(for: .connections, version: 2).path) + case .revoke(let data): + return data.url.appendingPathComponent( + "\(SENetPathBuilder(for: .connections, version: 2).path)/\(data.entityId)/revoke" + ) + } + } + + var headers: [String: String]? { + switch self { + case .createConnection(_, _, let appLanguage): + return Headers.requestHeaders(with: appLanguage) + case .revoke(let data): + return Headers.signedRequestHeaders( + token: data.accessToken, + payloadParams: parameters, + connectionGuid: data.connectionGuid + ) + } + } + + var parameters: [String: Any]? { + switch self { + case .createConnection(_, let data, _): + return RequestParametersBuilder.parameters(for: data) + case .revoke: + return [ + ParametersKeys.data: [:], + ParametersKeys.exp: Date().addingTimeInterval(5.0 * 60.0).utcSeconds + ] + } + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConsentRouterV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConsentRouterV2.swift new file mode 100644 index 00000000..dbde07e0 --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEConsentRouterV2.swift @@ -0,0 +1,78 @@ +// +// SEConsentRouterV2 +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation + +import Foundation +import SEAuthenticatorCore + +enum SEConsentRouter: Routable { + case list(SEBaseAuthenticatedRequestData) + case revoke(SEBaseAuthenticatedWithIdRequestData, [String: Any]) + + var method: HTTPMethod { + switch self { + case .list: return .get + case .revoke: return .put + } + } + + var encoding: Encoding { + switch self { + case .list: return .url + case .revoke: return .json + } + } + + var url: URL { + switch self { + case .list(let data): + return data.url.appendingPathComponent( + SENetPathBuilder(for: .consents, version: 2).path + ) + case .revoke(let data, _): + return data.url.appendingPathComponent( + "\(SENetPathBuilder(for: .consents, version: 2).path)/\(data.entityId)/revoke" + ) + } + } + + var parameters: [String: Any]? { + switch self { + case .list: return nil + case let .revoke(_, parameters): return parameters + } + } + + var headers: [String: String]? { + switch self { + case .list(let data): + return Headers.authorizedRequestHeaders(token: data.accessToken) + case let .revoke(data, parameters): + return Headers.signedRequestHeaders( + token: data.accessToken, + payloadParams: parameters, + connectionGuid: data.connectionGuid + ) + } + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEProviderRouterV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEProviderRouterV2.swift new file mode 100644 index 00000000..36b62208 --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Routers/SEProviderRouterV2.swift @@ -0,0 +1,52 @@ +// +// SEProviderRouterV2 +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +enum SEProviderRouterV2: Routable { + case fetchData(URL) + + var method: HTTPMethod { + return .get + } + + var encoding: Encoding { + return .url + } + + var url: URL { + switch self { + case .fetchData(let url): + print(url) + return url + } + } + + var headers: [String: String]? { + return Headers.requestHeaders(with: "en") + } + + var parameters: [String: Any]? { + return nil + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/SEEncryptedAuthorizationData.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/SEEncryptedAuthorizationData.swift new file mode 100644 index 00000000..84ce847c --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/SEEncryptedAuthorizationData.swift @@ -0,0 +1,56 @@ +// +// SEEncryptedAuthorizationData +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +public struct SEEncryptedAuthorizationData: SEBaseEncryptedAuthorizationData, Decodable { + public let id: String + public let data: String + public let key: String + public let iv: String + public let status: AuthorizationStatus + public var connectionId: String? + public var entityId: String? + + enum CodingKeys: String, CodingKey { + case id + case data + case key + case iv + case status + case connectionId = "connection_id" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let authorizationV2Id = try container.decode(Int.self, forKey: .id) + id = "\(authorizationV2Id)" + data = try container.decode(String.self, forKey: .data) + key = try container.decode(String.self, forKey: .key) + iv = try container.decode(String.self, forKey: .iv) + status = try container.decode(AuthorizationStatus.self, forKey: .status) + if let connectionV2Id = try container.decodeIfPresent(Int.self, forKey: .connectionId) { + connectionId = "\(connectionV2Id)" + } + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/JWSHelper.swift b/SaltedgeAuthenticatorSDKv2/Classes/JWSHelper.swift new file mode 100644 index 00000000..4e85c1a7 --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/JWSHelper.swift @@ -0,0 +1,52 @@ +// +// JWSHelper +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import JOSESwift +import SEAuthenticatorCore + +struct JWSHelper { + static func sign(params: [String: Any]?, guid: String) -> String? { + guard let payloadParams = params, + let payloadBody = ParametersSerializer.createBody(parameters: payloadParams), + let privateKey = privateKey(for: SETagHelper.create(for: guid)), + let signer = Signer(signingAlgorithm: .RS256, key: privateKey) else { return nil } + + let header = JWSHeader(algorithm: .RS256) + + guard let jws = try? JWS(header: header, payload: Payload(payloadBody), signer: signer) else { return nil } + + let splittedSerializedJwsString = jws.compactSerializedString.split(separator: ".") + + return splittedSerializedJwsString[0] + ".." + splittedSerializedJwsString[2] + } + + static func privateKey(for tag: String) -> SecKey? { + do { + return try SECryptoHelper.privateKey(for: tag) + } catch { + print(error.localizedDescription) + } + + return nil + } +} diff --git a/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift b/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift new file mode 100644 index 00000000..c74a59e5 --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/Classes/RequestParametersBuilder.swift @@ -0,0 +1,103 @@ +// +// RequestParametersBuilder +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2021 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Foundation +import SEAuthenticatorCore + +struct ParametersKeys { + static let data = "data" + static let key = "key" + static let iv = "iv" + static let actionId = "action_id" + static let connectionId = "connection_id" + static let providerId = "provider_id" + static let publicKey = "public_key" + static let deviceInfo = "device_info" + static let platform = "platform" + static let pushToken = "push_token" + static let returnUrl = "return_url" + static let connectQuery = "connect_query" + static let confirm = "confirm" + static let authorizationCode = "authorization_code" + static let credentials = "credentials" + static let encryptedRsaPublicKey = "enc_rsa_public_key" + static let exp = "exp" +} + +struct RequestParametersBuilder { + static func parameters(for connectionParams: SECreateConnectionParams) -> [String: Any] { + let encryptedRsaPublicKeyDict: [String: Any] = [ + ParametersKeys.data: connectionParams.encryptedRsaPublicKey.data, + ParametersKeys.key: connectionParams.encryptedRsaPublicKey.key, + ParametersKeys.iv: connectionParams.encryptedRsaPublicKey.iv, + ] + + var data: [String: Any] = [ + ParametersKeys.providerId: connectionParams.providerId, + ParametersKeys.returnUrl: SENetConstants.oauthRedirectUrl, + ParametersKeys.platform: "ios", + ParametersKeys.encryptedRsaPublicKey: encryptedRsaPublicKeyDict + ] + + if let pushToken = connectionParams.pushToken, !pushToken.isEmpty { + data = data.merge(with: [ParametersKeys.pushToken: pushToken]) + } + if let connectQuery = connectionParams.connectQuery, !connectQuery.isEmpty { + data = data.merge(with: [ParametersKeys.connectQuery: connectQuery]) + } + + return [ParametersKeys.data: data] + } + + static func confirmAuthorizationParams(encryptedData: SEEncryptedData?, exp: Int) -> [String: Any] { + guard let encryptedData = encryptedData else { return [:] } + + let encryptedDataParams = [ + SENetKeys.data: encryptedData.data, + SENetKeys.key: encryptedData.key, + SENetKeys.iv: encryptedData.iv + ] + + return [ + ParametersKeys.data: encryptedDataParams, + ParametersKeys.exp: exp + ] + } + + static func actionParameters(requestData: SEActionRequestDataV2) -> [String: Any] { + return [ + SENetKeys.data: [ + ParametersKeys.providerId: requestData.providerId, + ParametersKeys.actionId: requestData.actionId, + ParametersKeys.connectionId: requestData.connectionId + ], + ParametersKeys.exp: Date().addingTimeInterval(5.0 * 60.0).utcSeconds + ] + } + + static var expirationTimeParameters: [String: Any] { + return [ + ParametersKeys.data: [:], + ParametersKeys.exp: Date().addingTimeInterval(5.0 * 60.0).utcSeconds + ] + } +} diff --git a/SaltedgeAuthenticatorSDKv2/SaltedgeAuthenticatorSDKv2.podspec b/SaltedgeAuthenticatorSDKv2/SaltedgeAuthenticatorSDKv2.podspec new file mode 100644 index 00000000..dc75e5c8 --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/SaltedgeAuthenticatorSDKv2.podspec @@ -0,0 +1,33 @@ +# +# Be sure to run `pod lib lint SaltedgeAuthenticatorSDK.podspec' to ensure this is a +# valid spec before submitting. +# +# Any lines starting with a # are optional, but their use is encouraged +# To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html +# + +Pod::Spec.new do |s| + s.name = 'SaltedgeAuthenticatorSDKv2' + s.version = '1.1.0' + s.summary = 'SDK for decoupled authentication solution to meet the requirements of Strong Customer Authentication (SCA)' + + s.description = <<-DESC + Authenticator iOS SDK - is a module for connecting to Salt Edge Authenticator API of + Bank (Service Provider) System, that implements Strong Customer Authentication/Dynamic Linking process. + DESC + + s.homepage = 'https://github.com/saltedge/sca-authenticator-ios' + s.license = { :type => 'GPLv3', :file => '../LICENSE.txt' } + s.author = { 'Salt Edge Inc.' => 'authenticator@saltedge.com' } + s.source = { :git => 'https://github.com/saltedge/sca-authenticator-ios.git', :tag => s.version.to_s } + + s.ios.deployment_target = '10.0' + s.swift_version = '5' + s.module_name = 'SEAuthenticatorV2' + + s.source_files = '**/Classes/**/*' + + s.dependency 'SaltedgeAuthenticatorCore' +# s.dependency 'CryptoSwift' + s.dependency 'JOSESwift' +end From 6f0c41aa99dd03ac7af93ae17856651bb3f569aa Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Mon, 16 May 2022 18:09:14 +0300 Subject: [PATCH 086/110] fixed SEEncryptedData decoding --- .clabot | 3 +- .../Repositories/ConnectionRepository.swift | 11 ++--- .../Utils/Handlers/ConnectHandler.swift | 26 ++++++++-- .../Settings/SettingsViewController.swift | 14 +++--- .../AuthorizationHeaderView.swift | 7 +-- .../SEEncryptedDataExtensionsSpec.swift | 32 ++++++------- Example/Tests/Spec Helpers/SpecUtils.swift | 12 ++--- .../Classes/API/SEEncryptedData.swift | 48 +++++++++---------- .../API/Models/SEAuthorizationData.swift | 1 - .../API/Managers/SEProviderManagerV2.swift | 4 -- 10 files changed, 77 insertions(+), 81 deletions(-) diff --git a/.clabot b/.clabot index cbfa32a8..8b2b913a 100644 --- a/.clabot +++ b/.clabot @@ -3,8 +3,7 @@ "ConstantinKV", "baller784", "gorceagolga", - "mnewlive", - "alisnic" + "mnewlive" ], "message": "Thank you for your pull request and welcome to our [Salt Edge](https://www.saltedge.com/) community. We require contributors to [sign our Contributor License Agreement](https://forms.gle/nX4mFP8eg78NVgrVA), and we don't seem to have the users {{usersWithoutCLA}} on file. In order for us to review and merge your code, please contact the project maintainers to get yourself added, in case you have already signed CLA and not been added yet.", "label": "CLA-signed" diff --git a/Example/Authenticator/Models/Repositories/ConnectionRepository.swift b/Example/Authenticator/Models/Repositories/ConnectionRepository.swift index 9a58ecdc..495f9187 100644 --- a/Example/Authenticator/Models/Repositories/ConnectionRepository.swift +++ b/Example/Authenticator/Models/Repositories/ConnectionRepository.swift @@ -26,21 +26,18 @@ import SEAuthenticatorCore struct ConnectionRepository { @discardableResult - static func setAccessTokenAndActive(_ connection: Connection, accessToken token: String) -> Bool { - guard let decryptedTokenData = try? SECryptoHelper.decrypt(key: token, tag: SETagHelper.create(for: connection.guid)).json, - let decryptedAccessToken = decryptedTokenData[SENetKeys.accessToken] as? String else { - return false - } + static func setAccessTokenAndActive(_ connection: Connection, accessToken token: String?) -> Bool { + guard let token = token else { return false } var result = false if connection.isManaged { try? RealmManager.performRealmWriteTransaction { - connection.accessToken = decryptedAccessToken + connection.accessToken = token connection.status = ConnectionStatus.active.rawValue result = true } } else { - connection.accessToken = decryptedAccessToken + connection.accessToken = token connection.status = ConnectionStatus.active.rawValue result = true } diff --git a/Example/Authenticator/Utils/Handlers/ConnectHandler.swift b/Example/Authenticator/Utils/Handlers/ConnectHandler.swift index c5d0be7a..1b3a2d24 100644 --- a/Example/Authenticator/Utils/Handlers/ConnectHandler.swift +++ b/Example/Authenticator/Utils/Handlers/ConnectHandler.swift @@ -59,7 +59,11 @@ final class ConnectHandler { func saveConnectionAndFinish(with accessToken: AccessToken) { guard let connection = connection else { return } - ConnectionRepository.setAccessTokenAndActive(connection, accessToken: accessToken) + ConnectionRepository.setAccessTokenAndActive( + connection, + accessToken: connection.isApiV2 ? decryptedAccessToken(accessToken, for: connection) : accessToken + ) + ConnectionRepository.save(connection) let attributedConnectionName = NSAttributedString( string: connection.name, @@ -75,8 +79,11 @@ final class ConnectHandler { delegate?.requestLocationAuthorization() } } +} - private func fetchConfiguration(url: URL) { +// Private methods +private extension ConnectHandler { + func fetchConfiguration(url: URL) { guard let configurationUrl = SEConnectHelper.сonfiguration(from: url) else { return } let connectQuery = SEConnectHelper.connectQuery(from: url) @@ -91,7 +98,7 @@ final class ConnectHandler { ) } - private func createNewConnection( + func createNewConnection( from configurationUrl: URL, with connectQuery: String?, interactor: BaseConnectionsInteractor @@ -113,7 +120,7 @@ final class ConnectHandler { ) } - private func reconnectConnection(_ connectionId: String) { + func reconnectConnection(_ connectionId: String) { guard let connection = ConnectionsCollector.with(id: connectionId) else { return } let interactor: BaseConnectionsInteractor = connection.isApiV2 @@ -136,7 +143,16 @@ final class ConnectHandler { ) } - private func dismissConnectWithError(error: String) { + func decryptedAccessToken(_ token: String, for connection: Connection) -> String? { + guard let decryptedTokenData = try? SECryptoHelper.decrypt(key: token, tag: SETagHelper.create(for: connection.guid)).json, + let decryptedAccessToken = decryptedTokenData[SENetKeys.accessToken] as? String else { + return nil + } + + return decryptedAccessToken + } + + func dismissConnectWithError(error: String) { DispatchQueue.main.async { self.delegate?.dismissConnectWithError(error: error) } diff --git a/Example/Authenticator/View Controllers/Settings/SettingsViewController.swift b/Example/Authenticator/View Controllers/Settings/SettingsViewController.swift index dff6290f..4e302802 100644 --- a/Example/Authenticator/View Controllers/Settings/SettingsViewController.swift +++ b/Example/Authenticator/View Controllers/Settings/SettingsViewController.swift @@ -43,14 +43,14 @@ final class SettingsViewController: BaseViewController { layout() } -// override func viewWillAppear(_ animated: Bool) { -// super.viewWillAppear(animated) -// tableView.reloadData() -// } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + tableView.reloadData() + } -// func reloadData() { -// tableView.reloadData() -// } + func reloadData() { + tableView.reloadData() + } } // MARK: - Setup diff --git a/Example/Authenticator/Views/Authorizations/AuthorizationHeaderView.swift b/Example/Authenticator/Views/Authorizations/AuthorizationHeaderView.swift index 5f3fcda0..6e52572a 100644 --- a/Example/Authenticator/Views/Authorizations/AuthorizationHeaderView.swift +++ b/Example/Authenticator/Views/Authorizations/AuthorizationHeaderView.swift @@ -42,12 +42,7 @@ final class AuthorizationHeaderView: RoundedShadowView { return view }() private let connectionImageView = AspectFitImageView(imageName: "") - private let connectionNameLabel: UILabel = { - let label = UILabel() - label.font = .systemFont(ofSize: 14.0) - label.textColor = .titleColor - return label - }() + private let connectionNameLabel = UILabel(font: .systemFont(ofSize: 14.0), textColor: .titleColor) private let timeLeftLabel = TimeLeftLabel() private let progressView = CountdownProgressView() diff --git a/Example/Tests/Extensions/SEEncryptedDataExtensionsSpec.swift b/Example/Tests/Extensions/SEEncryptedDataExtensionsSpec.swift index 5b027725..8d834098 100644 --- a/Example/Tests/Extensions/SEEncryptedDataExtensionsSpec.swift +++ b/Example/Tests/Extensions/SEEncryptedDataExtensionsSpec.swift @@ -42,17 +42,17 @@ class SEEncryptedDataExtensionsSpec: BaseSpec { "description": "Test authorization", "created_at": Date().iso8601string, "expires_at": Date().addingTimeInterval(5.0 * 60.0).iso8601string] + let encryptedData = try! SECryptoHelper.encrypt(authMessage.jsonString!, tag: SETagHelper.create(for: connection.guid)) - let dict = [ - "data": encryptedData.data, - "key": encryptedData.key, - "iv": encryptedData.iv, - "connection_id": connection.id, - "algorithm": "AES-256-CBC" - ] let expectedData = SEAuthorizationData(authMessage) + let actualData = SEEncryptedData( + data: encryptedData.data, + key: encryptedData.key, + iv: encryptedData.iv, + connectionId: connection.id + ) - expect(expectedData).to(equal(SEEncryptedData(dict)!.decryptedAuthorizationData!)) + expect(expectedData).to(equal(actualData.decryptedAuthorizationData!)) } } @@ -67,16 +67,16 @@ class SEEncryptedDataExtensionsSpec: BaseSpec { "created_at": Date().iso8601string, "expires_at": Date().addingTimeInterval(5.0 * 60.0).iso8601string, "authorization_code": "some code"] + let encryptedData = try! SECryptoHelper.encrypt(authMessage.jsonString!, tag: SETagHelper.create(for: connection.guid)) - let dict = [ - "data": encryptedData.data, - "key": encryptedData.key, - "iv": encryptedData.iv, - "connection_id": connection.id, - "algorithm": "AES-256-CBC" - ] + let actualData = SEEncryptedData( + data: encryptedData.data, + key: encryptedData.key, + iv: encryptedData.iv, + connectionId: connection.id + ) - expect(SEEncryptedData(dict)!.decryptedAuthorizationData).to(beNil()) + expect(actualData.decryptedAuthorizationData).to(beNil()) } } } diff --git a/Example/Tests/Spec Helpers/SpecUtils.swift b/Example/Tests/Spec Helpers/SpecUtils.swift index 05d0f197..4d315520 100644 --- a/Example/Tests/Spec Helpers/SpecUtils.swift +++ b/Example/Tests/Spec Helpers/SpecUtils.swift @@ -48,17 +48,11 @@ struct SpecUtils { } static func createAuthResponse(with authMessage: [String: Any], id: ID, guid: GUID) -> SEAuthorizationData { - let encryptedData = try! SECryptoHelper.encrypt(authMessage.jsonString!, tag: SETagHelper.create(for: guid)) + let encryptedMessage = try! SECryptoHelper.encrypt(authMessage.jsonString!, tag: SETagHelper.create(for: guid)) - let dict = [ - "data": encryptedData.data, - "key": encryptedData.key, - "iv": encryptedData.iv, - "connection_id": id, - "algorithm": "AES-256-CBC" - ] + let encryptedData = SEEncryptedData(data: encryptedMessage.data, key: encryptedMessage.key, iv: encryptedMessage.iv, connectionId: id) - return SEEncryptedData(dict)!.decryptedAuthorizationData! + return encryptedData.decryptedAuthorizationData! } static func createAuthResponseV2( diff --git a/SaltedgeAuthenticatorCore/Classes/API/SEEncryptedData.swift b/SaltedgeAuthenticatorCore/Classes/API/SEEncryptedData.swift index 293a428c..9d36d22d 100644 --- a/SaltedgeAuthenticatorCore/Classes/API/SEEncryptedData.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/SEEncryptedData.swift @@ -31,33 +31,33 @@ public struct SEEncryptedData: SEBaseEncryptedAuthorizationData, Decodable, Equa public var connectionId: String? public var entityId: String? - public init(data: String, key: String, iv: String) { - self.data = data - self.key = key - self.iv = iv + enum CodingKeys: String, CodingKey { + case id + case connectionId = "connection_id" + case data + case key + case iv } - public init?(_ value: Any) { - print("VALUE: \(value as? [String: Any])") - - if let dict = value as? [String: Any], - let data = dict[SENetKeys.data] as? String, - let key = dict[SENetKeys.key] as? String, - let iv = dict[SENetKeys.iv] as? String { - self.data = data - self.key = key - self.iv = iv - if let entityId = dict[SENetKeys.id] as? Int { - self.entityId = "\(entityId)" - } - if let connectionId = dict[SENetKeys.connectionId] as? String { - self.connectionId = connectionId - } else if let connectionId = dict[SENetKeys.connectionId] as? Int { // NOTE: connection_id in v2 is integer - self.connectionId = "\(connectionId)" - } - } else { - return nil + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + data = try container.decode(String.self, forKey: .data) + key = try container.decode(String.self, forKey: .key) + iv = try container.decode(String.self, forKey: .iv) + if let connectionIdString = try container.decodeIfPresent(String.self, forKey: .connectionId) { + connectionId = connectionIdString + } else if let id = try container.decodeIfPresent(Int.self, forKey: .connectionId) { + // NOTE: connection_id in v2 is integer + connectionId = "\(id)" } + entityId = try container.decodeIfPresent(String.self, forKey: .id) + } + + public init(data: String, key: String, iv: String, connectionId: String? = nil) { + self.data = data + self.key = key + self.iv = iv + self.connectionId = connectionId } public static func == (lhs: SEEncryptedData, rhs: SEEncryptedData) -> Bool { diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Models/SEAuthorizationData.swift b/SaltedgeAuthenticatorSDK/Classes/API/Models/SEAuthorizationData.swift index 4407ae1c..a26e7ed6 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Models/SEAuthorizationData.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Models/SEAuthorizationData.swift @@ -34,7 +34,6 @@ public class SEAuthorizationData: SEBaseAuthorizationData { public var apiVersion: ApiVersion = "1" public init?(_ dictionary: [String: Any]) { - print("$$$$$ \(dictionary)") if let id = dictionary[SENetKeys.id] as? String, let connectionId = dictionary[SENetKeys.connectionId] as? String, let title = dictionary[SENetKeys.title] as? String, diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEProviderManagerV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEProviderManagerV2.swift index 6226fee6..b6618ae9 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEProviderManagerV2.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEProviderManagerV2.swift @@ -36,7 +36,3 @@ public struct SEProviderManagerV2 { ) } } -<<<<<<< HEAD -======= - ->>>>>>> 3879f5d4c44648af38206e8da51e8fd7df22603d From 1a0d4b50ba7094cf38fe1ddc7be1bac13789e11b Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Wed, 18 May 2022 14:49:01 +0300 Subject: [PATCH 087/110] updated pods --- Example/Podfile.lock | 176 +++++++++++++++++++++++-------------------- 1 file changed, 94 insertions(+), 82 deletions(-) diff --git a/Example/Podfile.lock b/Example/Podfile.lock index 9e0eda50..80e61092 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -1,89 +1,99 @@ PODS: - - CryptoSwift (1.4.1) - - Firebase/Analytics (8.4.0): + - CryptoSwift (1.5.1) + - Firebase/Analytics (9.0.0): - Firebase/Core - - Firebase/Core (8.4.0): + - Firebase/Core (9.0.0): - Firebase/CoreOnly - - FirebaseAnalytics (~> 8.4.0) - - Firebase/CoreOnly (8.4.0): - - FirebaseCore (= 8.4.0) - - Firebase/Crashlytics (8.4.0): + - FirebaseAnalytics (~> 9.0.0) + - Firebase/CoreOnly (9.0.0): + - FirebaseCore (= 9.0.0) + - Firebase/Crashlytics (9.0.0): - Firebase/CoreOnly - - FirebaseCrashlytics (~> 8.4.0) - - FirebaseAnalytics (8.4.0): - - FirebaseAnalytics/AdIdSupport (= 8.4.0) - - FirebaseCore (~> 8.0) - - FirebaseInstallations (~> 8.0) - - GoogleUtilities/AppDelegateSwizzler (~> 7.4) - - GoogleUtilities/MethodSwizzler (~> 7.4) - - GoogleUtilities/Network (~> 7.4) - - "GoogleUtilities/NSData+zlib (~> 7.4)" + - FirebaseCrashlytics (~> 9.0.0) + - FirebaseAnalytics (9.0.0): + - FirebaseAnalytics/AdIdSupport (= 9.0.0) + - FirebaseCore (~> 9.0) + - FirebaseInstallations (~> 9.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.7) + - GoogleUtilities/MethodSwizzler (~> 7.7) + - GoogleUtilities/Network (~> 7.7) + - "GoogleUtilities/NSData+zlib (~> 7.7)" - nanopb (~> 2.30908.0) - - FirebaseAnalytics/AdIdSupport (8.4.0): - - FirebaseCore (~> 8.0) - - FirebaseInstallations (~> 8.0) - - GoogleAppMeasurement (= 8.4.0) - - GoogleUtilities/AppDelegateSwizzler (~> 7.4) - - GoogleUtilities/MethodSwizzler (~> 7.4) - - GoogleUtilities/Network (~> 7.4) - - "GoogleUtilities/NSData+zlib (~> 7.4)" + - FirebaseAnalytics/AdIdSupport (9.0.0): + - FirebaseCore (~> 9.0) + - FirebaseInstallations (~> 9.0) + - GoogleAppMeasurement (= 9.0.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.7) + - GoogleUtilities/MethodSwizzler (~> 7.7) + - GoogleUtilities/Network (~> 7.7) + - "GoogleUtilities/NSData+zlib (~> 7.7)" - nanopb (~> 2.30908.0) - - FirebaseCore (8.4.0): - - FirebaseCoreDiagnostics (~> 8.0) - - GoogleUtilities/Environment (~> 7.4) - - GoogleUtilities/Logger (~> 7.4) - - FirebaseCoreDiagnostics (8.4.0): - - GoogleDataTransport (~> 9.0) - - GoogleUtilities/Environment (~> 7.4) - - GoogleUtilities/Logger (~> 7.4) + - FirebaseCore (9.0.0): + - FirebaseCoreDiagnostics (~> 9.0) + - FirebaseCoreInternal (~> 9.0) + - GoogleUtilities/Environment (~> 7.7) + - GoogleUtilities/Logger (~> 7.7) + - FirebaseCoreDiagnostics (9.0.0): + - GoogleDataTransport (~> 9.1) + - GoogleUtilities/Environment (~> 7.7) + - GoogleUtilities/Logger (~> 7.7) - nanopb (~> 2.30908.0) - - FirebaseCrashlytics (8.4.0): - - FirebaseCore (~> 8.0) - - FirebaseInstallations (~> 8.0) - - GoogleDataTransport (~> 9.0) - - GoogleUtilities/Environment (~> 7.4) + - FirebaseCoreInternal (9.0.0): + - "GoogleUtilities/NSData+zlib (~> 7.7)" + - FirebaseCrashlytics (9.0.0): + - FirebaseCore (~> 9.0) + - FirebaseInstallations (~> 9.0) + - GoogleDataTransport (~> 9.1) + - GoogleUtilities/Environment (~> 7.7) - nanopb (~> 2.30908.0) - - PromisesObjC (< 3.0, >= 1.2) - - FirebaseInstallations (8.4.0): - - FirebaseCore (~> 8.0) - - GoogleUtilities/Environment (~> 7.4) - - GoogleUtilities/UserDefaults (~> 7.4) - - PromisesObjC (< 3.0, >= 1.2) - - GoogleAppMeasurement (8.4.0): - - GoogleAppMeasurement/AdIdSupport (= 8.4.0) - - GoogleUtilities/AppDelegateSwizzler (~> 7.4) - - GoogleUtilities/MethodSwizzler (~> 7.4) - - GoogleUtilities/Network (~> 7.4) - - "GoogleUtilities/NSData+zlib (~> 7.4)" + - PromisesObjC (~> 2.1) + - FirebaseInstallations (9.0.0): + - FirebaseCore (~> 9.0) + - GoogleUtilities/Environment (~> 7.7) + - GoogleUtilities/UserDefaults (~> 7.7) + - PromisesObjC (~> 2.1) + - GoogleAppMeasurement (9.0.0): + - GoogleAppMeasurement/AdIdSupport (= 9.0.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.7) + - GoogleUtilities/MethodSwizzler (~> 7.7) + - GoogleUtilities/Network (~> 7.7) + - "GoogleUtilities/NSData+zlib (~> 7.7)" - nanopb (~> 2.30908.0) - - GoogleAppMeasurement/AdIdSupport (8.4.0): - - GoogleUtilities/AppDelegateSwizzler (~> 7.4) - - GoogleUtilities/MethodSwizzler (~> 7.4) - - GoogleUtilities/Network (~> 7.4) - - "GoogleUtilities/NSData+zlib (~> 7.4)" + - GoogleAppMeasurement/AdIdSupport (9.0.0): + - GoogleAppMeasurement/WithoutAdIdSupport (= 9.0.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.7) + - GoogleUtilities/MethodSwizzler (~> 7.7) + - GoogleUtilities/Network (~> 7.7) + - "GoogleUtilities/NSData+zlib (~> 7.7)" - nanopb (~> 2.30908.0) - - GoogleDataTransport (9.1.0): - - GoogleUtilities/Environment (~> 7.2) + - GoogleAppMeasurement/WithoutAdIdSupport (9.0.0): + - GoogleUtilities/AppDelegateSwizzler (~> 7.7) + - GoogleUtilities/MethodSwizzler (~> 7.7) + - GoogleUtilities/Network (~> 7.7) + - "GoogleUtilities/NSData+zlib (~> 7.7)" - nanopb (~> 2.30908.0) + - GoogleDataTransport (9.1.4): + - GoogleUtilities/Environment (~> 7.7) + - nanopb (< 2.30910.0, >= 2.30908.0) - PromisesObjC (< 3.0, >= 1.2) - - GoogleUtilities/AppDelegateSwizzler (7.5.0): + - GoogleUtilities/AppDelegateSwizzler (7.7.0): - GoogleUtilities/Environment - GoogleUtilities/Logger - GoogleUtilities/Network - - GoogleUtilities/Environment (7.5.0): + - GoogleUtilities/Environment (7.7.0): - PromisesObjC (< 3.0, >= 1.2) - - GoogleUtilities/Logger (7.5.0): + - GoogleUtilities/Logger (7.7.0): - GoogleUtilities/Environment - - GoogleUtilities/MethodSwizzler (7.5.0): + - GoogleUtilities/MethodSwizzler (7.7.0): - GoogleUtilities/Logger - - GoogleUtilities/Network (7.5.0): + - GoogleUtilities/Network (7.7.0): - GoogleUtilities/Logger - "GoogleUtilities/NSData+zlib" - GoogleUtilities/Reachability - - "GoogleUtilities/NSData+zlib (7.5.0)" - - GoogleUtilities/Reachability (7.5.0): + - "GoogleUtilities/NSData+zlib (7.7.0)" + - GoogleUtilities/Reachability (7.7.0): - GoogleUtilities/Logger - - GoogleUtilities/UserDefaults (7.5.0): + - GoogleUtilities/UserDefaults (7.7.0): - GoogleUtilities/Logger - JOSESwift (2.4.0) - nanopb (2.30908.0): @@ -91,9 +101,9 @@ PODS: - nanopb/encode (= 2.30908.0) - nanopb/decode (2.30908.0) - nanopb/encode (2.30908.0) - - Nimble (9.2.0) - - PromisesObjC (2.0.0) - - Quick (4.0.0) + - Nimble (10.0.0) + - PromisesObjC (2.1.0) + - Quick (5.0.1) - ReachabilitySwift (5.0.0) - Realm (5.5.1): - Realm/Headers (= 5.5.1) @@ -111,7 +121,7 @@ PODS: - SDWebImage (5.10.4): - SDWebImage/Core (= 5.10.4) - SDWebImage/Core (5.10.4) - - TinyConstraints (4.0.1) + - TinyConstraints (4.0.2) - Valet (3.2.8) DEPENDENCIES: @@ -135,6 +145,7 @@ SPEC REPOS: - FirebaseAnalytics - FirebaseCore - FirebaseCoreDiagnostics + - FirebaseCoreInternal - FirebaseCrashlytics - FirebaseInstallations - GoogleAppMeasurement @@ -161,21 +172,22 @@ EXTERNAL SOURCES: :path: "../SaltedgeAuthenticatorSDKv2" SPEC CHECKSUMS: - CryptoSwift: 0bc800a7e6a24c4fc9ebeab97d44b0d5f73a78bd - Firebase: 54cdc8bc9c9b3de54f43dab86e62f5a76b47034f - FirebaseAnalytics: 4751d6a49598a2b58da678cc07df696bcd809ab9 - FirebaseCore: 31f389c37ac1ea52454a53d3081f2d7019485a4a - FirebaseCoreDiagnostics: cad03be1904b975f845e632f2720c3337da27faf - FirebaseCrashlytics: c9eb562b2f6bd5ee5e880144fd5ef1bfe46c5dc5 - FirebaseInstallations: 1585729afc787877763208c2088ed84221161f77 - GoogleAppMeasurement: 6b6a08fd9c71f4dbc89e0e812acca81d797aa342 - GoogleDataTransport: 85fd18ff3019bb85d3f2c551d04c481dedf71fc9 - GoogleUtilities: eea970f4a389963963bffe8d8fabe43540678b9c + CryptoSwift: c4f2debceb38bf44c80659afe009f71e23e4a082 + Firebase: a876fadc5ea653a377693376fd4f885c62704512 + FirebaseAnalytics: ea4f6f4b604a20b4de4d3c7f7a2cb51d9a989040 + FirebaseCore: e4c0b5d9727eaee0b43f9ed00baff7500c188d7b + FirebaseCoreDiagnostics: 54410e5d156bf406a764c2722d9f77d682723b4c + FirebaseCoreInternal: 5b8f4f2e2970a4cb9bd1cf7ada16c8ba69a29530 + FirebaseCrashlytics: 08561398868790fc9694b85efb4c080940926fcc + FirebaseInstallations: e693c0dfe404af44afbd553de42498b2ca1ca189 + GoogleAppMeasurement: 2c2792d43ebdea0524adbc90cba9139721f3039b + GoogleDataTransport: 5fffe35792f8b96ec8d6775f5eccd83c998d5a3b + GoogleUtilities: e0913149f6b0625b553d70dae12b49fc62914fd1 JOSESwift: 7ff178bb9173ff42c6e990929a9f2fa702a34f69 nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96 - Nimble: 4f4a345c80b503b3ea13606a4f98405974ee4d0b - PromisesObjC: 68159ce6952d93e17b2dfe273b8c40907db5ba58 - Quick: 6473349e43b9271a8d43839d9ba1c442ed1b7ac4 + Nimble: 5316ef81a170ce87baf72dd961f22f89a602ff84 + PromisesObjC: 99b6f43f9e1044bd87a95a60beff28c2c44ddb72 + Quick: 749aa754fd1e7d984f2000fe051e18a3a9809179 ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 Realm: c2ffe0667f9c98c9ba65f608ba85684d39826145 RealmSwift: 5a35c1c35c3e1925e6fcd092c881d5cc4a826bff @@ -183,7 +195,7 @@ SPEC CHECKSUMS: SaltedgeAuthenticatorSDK: 4fd566984678e8d936cae541aee82c89b44422e2 SaltedgeAuthenticatorSDKv2: f9eb801575c0b90879c3f7cf667a7aabed743eef SDWebImage: c666b97e1fa9c64b4909816a903322018f0a9c84 - TinyConstraints: 8dd91295e56797648c7bc335dd20e1d91ec4c192 + TinyConstraints: 7b7ccc0c485bb3bb47082138ff28bc33cd49897f Valet: 16d0537d70db79d9ba953b9060b5da4fb8004e51 PODFILE CHECKSUM: 7f15a08d33a1af46d3f787184ec65b0ed568eed4 From 4e8873377dc8a8810d259be200adb4c56025e3f1 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Thu, 19 May 2022 15:25:17 +0300 Subject: [PATCH 088/110] addded decrypt acccess token spec --- .../Authenticator.xcodeproj/project.pbxproj | 12 +++++ Example/Tests/Crypto/SECryptoHelperSpec.swift | 48 +++++++++++++++++++ .../Tests/Spec Helpers/SpecCryptoHelper.swift | 36 ++++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 Example/Tests/Crypto/SECryptoHelperSpec.swift diff --git a/Example/Authenticator.xcodeproj/project.pbxproj b/Example/Authenticator.xcodeproj/project.pbxproj index 320cf273..eea21ea0 100644 --- a/Example/Authenticator.xcodeproj/project.pbxproj +++ b/Example/Authenticator.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 0479C5522387F8C300F3FE0A /* AVCaptureHelperSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0479C5512387F8C300F3FE0A /* AVCaptureHelperSpec.swift */; }; + 5DF91D13283516B300B80A9B /* SECryptoHelperSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DF91D12283516B300B80A9B /* SECryptoHelperSpec.swift */; }; 6CF5A0163F3E926E5B271FBE /* Pods_Authenticator_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC7966F04DE3EFEB4C47030C /* Pods_Authenticator_Tests.framework */; }; 8432878826CE378400F77DD5 /* AuthorizationStatusSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8432878726CE378400F77DD5 /* AuthorizationStatusSpec.swift */; }; 8434188C26BD33180027CF1B /* ConnectViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8434188B26BD33180027CF1B /* ConnectViewModel.swift */; }; @@ -398,6 +399,7 @@ 1249D71A41FFB9C79FF0AE5E /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 5A77B827BD85D2BAD4D1123E /* Pods-Authenticator_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Authenticator_Tests.debug.xcconfig"; path = "Target Support Files/Pods-Authenticator_Tests/Pods-Authenticator_Tests.debug.xcconfig"; sourceTree = ""; }; 5C3EEC2ACBD5A3E62C348862 /* Pods-Authenticator_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Authenticator_Example.release.xcconfig"; path = "Target Support Files/Pods-Authenticator_Example/Pods-Authenticator_Example.release.xcconfig"; sourceTree = ""; }; + 5DF91D12283516B300B80A9B /* SECryptoHelperSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SECryptoHelperSpec.swift; sourceTree = ""; }; 607FACD01AFB9204008FA782 /* Authenticator_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Authenticator_Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 607FACE51AFB9204008FA782 /* Authenticator_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Authenticator_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -662,6 +664,14 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 5DF91D112835169E00B80A9B /* Crypto */ = { + isa = PBXGroup; + children = ( + 5DF91D12283516B300B80A9B /* SECryptoHelperSpec.swift */, + ); + path = Crypto; + sourceTree = ""; + }; 607FACC71AFB9204008FA782 = { isa = PBXGroup; children = ( @@ -709,6 +719,7 @@ 607FACE81AFB9204008FA782 /* Tests */ = { isa = PBXGroup; children = ( + 5DF91D112835169E00B80A9B /* Crypto */, 9D69071A2655437E00A9CE94 /* v2 */, 9DCBB2872435F1EF00DB3F85 /* ViewModels */, 9D66E1D222D6146800BD59B6 /* Networking */, @@ -2064,6 +2075,7 @@ 959E30B3247BDA250067354A /* AboutViewModelSpec.swift in Sources */, 9DE3248022D3999200EB162A /* ConnectViewCoordinator.swift in Sources */, 9DE324C422D39A7300EB162A /* SetupAppViewController.swift in Sources */, + 5DF91D13283516B300B80A9B /* SECryptoHelperSpec.swift in Sources */, 9D66E1AC22D4C1CC00BD59B6 /* ApiErrorsSpec.swift in Sources */, 95C6782624AA0EE40088A9CF /* CollectionsInteractor.swift in Sources */, 95C8C0EF25F272CC005E787F /* LocationManager.swift in Sources */, diff --git a/Example/Tests/Crypto/SECryptoHelperSpec.swift b/Example/Tests/Crypto/SECryptoHelperSpec.swift new file mode 100644 index 00000000..9033529f --- /dev/null +++ b/Example/Tests/Crypto/SECryptoHelperSpec.swift @@ -0,0 +1,48 @@ +// +// SECryptoHelperSpec +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2022 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import Quick +import Nimble +@testable import SEAuthenticatorCore + +class SECryptoHelperSpec: BaseSpec { + override func spec() { + let connection: Connection = { + let c = Connection() + c.id = "1234" + SECryptoHelper.createKeyPair(with: SETagHelper.create(for: c.guid)) + return c + }() + let tag = SETagHelper.create(for: connection.guid) + + describe("decrypt(key:, tag:)") { + it("should decrypt message") { + let accessTokenMessage = "{\"access_token\": \"123456\"}" + let encryptedToken = try! SpecCryptoHelper.publicEncrypt(data: accessTokenMessage.data(using: .utf8)!, keyForTag: tag) + + let expectedDecryptedToken = try! SECryptoHelper.decrypt(key: encryptedToken.base64EncodedString(), tag: tag) + + expect(expectedDecryptedToken.json!["access_token"] as? String).to(equal("123456")) + } + } + } +} diff --git a/Example/Tests/Spec Helpers/SpecCryptoHelper.swift b/Example/Tests/Spec Helpers/SpecCryptoHelper.swift index 8cd16d6b..64ef2fa5 100644 --- a/Example/Tests/Spec Helpers/SpecCryptoHelper.swift +++ b/Example/Tests/Spec Helpers/SpecCryptoHelper.swift @@ -108,6 +108,42 @@ struct SpecCryptoHelper { return try obtainKey(for: tag) } + + static func publicEncrypt(data: Data, keyForTag: KeyTag) throws -> Data { + let publicKey = try obtainKey(for: keyForTag) + + let blockSize = SecKeyGetBlockSize(publicKey) + let maxChunkSize = blockSize - 11 // Since PKCS1 padding is used + + var decryptedDataAsArray = [UInt8](repeating: 0, count: data.count) + (data as NSData).getBytes(&decryptedDataAsArray, length: data.count) + + var encryptedDataBytes = [UInt8](repeating: 0, count: 0) + var idx = 0 + while idx < decryptedDataAsArray.count { + + let idxEnd = min(idx + maxChunkSize, decryptedDataAsArray.count) + let chunkData = [UInt8](decryptedDataAsArray[idx...allocate(capacity: encryptedDataBytes.count) + uint8Pointer.initialize(from: &encryptedDataBytes, count: encryptedDataBytes.count) + + return Data(bytes: uint8Pointer, count: encryptedDataBytes.count) + } } extension Data { From 9530066345bd86eb3720871b0b80adf834e4ad0f Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Wed, 1 Jun 2022 11:39:35 +0300 Subject: [PATCH 089/110] added polling for single authorization --- .../AuthorizationsCoordinator.swift | 5 ++ .../Connect/InstantActionCoordinator.swift | 6 +- .../Connect/QRCodeCoordinator.swift | 8 +- .../Coordinators/ConnectViewCoordinator.swift | 3 + .../Main/ApplicationCoordinator.swift | 2 + .../Connection/ConnectViewController.swift | 9 ++- .../QRCodeViewController.swift | 3 + .../SingleAuthorizationViewController.swift | 2 + .../AuthorizationsViewModel.swift | 2 +- .../SingleAuthorizationViewModel.swift | 76 +++++++++++++++---- .../Responses/SESubmitActionResponseV2.swift | 10 ++- 11 files changed, 102 insertions(+), 24 deletions(-) diff --git a/Example/Authenticator/Coordinators/AuthorizationsCoordinator.swift b/Example/Authenticator/Coordinators/AuthorizationsCoordinator.swift index 99fd44d0..4d8751b9 100644 --- a/Example/Authenticator/Coordinators/AuthorizationsCoordinator.swift +++ b/Example/Authenticator/Coordinators/AuthorizationsCoordinator.swift @@ -70,7 +70,12 @@ extension AuthorizationsCoordinator: AuthorizationsViewControllerDelegate { return } + stop() // Stop pollling when presenting contollers + qrCoordinator = QRCodeCoordinator(rootViewController: rootViewController) + qrCoordinator?.shouldDismissController = { + self.viewModel.setupPolling() + } qrCoordinator?.start() } diff --git a/Example/Authenticator/Coordinators/Connect/InstantActionCoordinator.swift b/Example/Authenticator/Coordinators/Connect/InstantActionCoordinator.swift index 4791daec..fbd4b47d 100644 --- a/Example/Authenticator/Coordinators/Connect/InstantActionCoordinator.swift +++ b/Example/Authenticator/Coordinators/Connect/InstantActionCoordinator.swift @@ -30,6 +30,8 @@ final class InstantActionCoordinator: Coordinator { private var instantActionHandler: InstantActionHandler private var qrCodeCoordinator: QRCodeCoordinator? + var shouldDismissController: (() -> ())? + init(rootViewController: UIViewController, qrUrl: URL) { self.rootViewController = rootViewController self.connectViewController = ConnectViewController() @@ -41,6 +43,7 @@ final class InstantActionCoordinator: Coordinator { func start() { connectViewController.title = l10n(.newAction) + connectViewController.shouldDismiss = shouldDismissController rootViewController.present( UINavigationController(rootViewController: connectViewController), animated: true @@ -84,13 +87,14 @@ extension InstantActionCoordinator: InstantActionEventsDelegate { } func shouldDismiss() { - connectViewController.dismiss(animated: true) + connectViewController.dismiss(animated: true, completion: shouldDismissController) } func shouldDismiss(with error: String) { connectViewController.dismiss( animated: true, completion: { + self.shouldDismissController?() self.rootViewController.present(message: error) } ) diff --git a/Example/Authenticator/Coordinators/Connect/QRCodeCoordinator.swift b/Example/Authenticator/Coordinators/Connect/QRCodeCoordinator.swift index c0b0a9b1..b799b199 100644 --- a/Example/Authenticator/Coordinators/Connect/QRCodeCoordinator.swift +++ b/Example/Authenticator/Coordinators/Connect/QRCodeCoordinator.swift @@ -27,7 +27,9 @@ final class QRCodeCoordinator: Coordinator { private var rootViewController: UIViewController private var qrCodeViewController: QRCodeViewController private var connectViewCoordinator: ConnectViewCoordinator? - private var instantActionCoordinator: InstantActionCoordinator? + private var instantActionCoordinator: InstantActionCoordinator? + + var shouldDismissController: (() -> ())? init(rootViewController: UIViewController) { self.rootViewController = rootViewController @@ -36,6 +38,8 @@ final class QRCodeCoordinator: Coordinator { func start() { qrCodeViewController.delegate = self + qrCodeViewController.shouldDismissClosure = shouldDismissController + if let navController = rootViewController.navigationController { navController.present(qrCodeViewController, animated: true) } else { @@ -57,12 +61,14 @@ extension QRCodeCoordinator: QRCodeViewControllerDelegate { rootViewController: rootViewController, qrUrl: url ) + instantActionCoordinator?.shouldDismissController = shouldDismissController instantActionCoordinator?.start() } else { connectViewCoordinator = ConnectViewCoordinator( rootViewController: rootViewController, connectionType: .newConnection(data) ) + connectViewCoordinator?.shouldDismissController = shouldDismissController connectViewCoordinator?.start() } } diff --git a/Example/Authenticator/Coordinators/ConnectViewCoordinator.swift b/Example/Authenticator/Coordinators/ConnectViewCoordinator.swift index 3f54fabf..5ee044a7 100644 --- a/Example/Authenticator/Coordinators/ConnectViewCoordinator.swift +++ b/Example/Authenticator/Coordinators/ConnectViewCoordinator.swift @@ -50,6 +50,8 @@ final class ConnectViewCoordinator: Coordinator { private var connection: Connection? private let connectionType: ConnectionType + var shouldDismissController: (() -> ())? + init(rootViewController: UIViewController, connectionType: ConnectionType) { self.rootViewController = rootViewController self.connectionType = connectionType @@ -59,6 +61,7 @@ final class ConnectViewCoordinator: Coordinator { func start() { connectHandler?.delegate = self connectHandler?.startHandling() + connectViewController.shouldDismiss = shouldDismissController rootViewController.present( UINavigationController(rootViewController: connectViewController), diff --git a/Example/Authenticator/Coordinators/Main/ApplicationCoordinator.swift b/Example/Authenticator/Coordinators/Main/ApplicationCoordinator.swift index deec350b..895538da 100644 --- a/Example/Authenticator/Coordinators/Main/ApplicationCoordinator.swift +++ b/Example/Authenticator/Coordinators/Main/ApplicationCoordinator.swift @@ -236,6 +236,8 @@ final class ApplicationCoordinator: Coordinator { } private func startConnect(url: URL, controller: UIViewController) { + authorizationsCoordinator.stop() + if SEConnectHelper.shouldStartInstantActionFlow(url: url) { instantActionCoordinator = InstantActionCoordinator( rootViewController: controller, diff --git a/Example/Authenticator/View Controllers/Connection/ConnectViewController.swift b/Example/Authenticator/View Controllers/Connection/ConnectViewController.swift index 777c2404..90134ebc 100644 --- a/Example/Authenticator/View Controllers/Connection/ConnectViewController.swift +++ b/Example/Authenticator/View Controllers/Connection/ConnectViewController.swift @@ -26,12 +26,19 @@ final class ConnectViewController: BaseViewController { private lazy var completeView = CompleteView(state: .processing, title: l10n(.processing)) private let viewModel = ConnectViewModel(reachabilityManager: ConnectivityManager.shared) + var shouldDismiss: (() ->())? + override func viewDidLoad() { super.viewDidLoad() viewModel.checkInternetConnection() setupCancelButton() layout() } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + shouldDismiss?() + } } // MARK: - Layout @@ -55,7 +62,7 @@ private extension ConnectViewController { } @objc func cancelPressed() { - dismiss(animated: true) + dismiss(animated: true, completion: shouldDismiss) } } diff --git a/Example/Authenticator/View Controllers/QRCodeViewController.swift b/Example/Authenticator/View Controllers/QRCodeViewController.swift index cdd0a3d6..06919d3a 100644 --- a/Example/Authenticator/View Controllers/QRCodeViewController.swift +++ b/Example/Authenticator/View Controllers/QRCodeViewController.swift @@ -49,6 +49,9 @@ final class QRCodeViewController: BaseViewController { override func viewDidLoad() { super.viewDidLoad() requestCameraPermission() + if #available(iOS 13.0, *) { + isModalInPresentation = true + } } override func viewWillAppear(_ animated: Bool) { diff --git a/Example/Authenticator/View Controllers/SingleAuthorizationViewController.swift b/Example/Authenticator/View Controllers/SingleAuthorizationViewController.swift index 2102e167..7d277c55 100644 --- a/Example/Authenticator/View Controllers/SingleAuthorizationViewController.swift +++ b/Example/Authenticator/View Controllers/SingleAuthorizationViewController.swift @@ -58,10 +58,12 @@ final class SingleAuthorizationViewController: BaseViewController { override func viewWillDisappear(_ animated: Bool) { super.viewDidDisappear(animated) + viewModel.stopPolling() stopTimer() } deinit { + viewModel.stopPolling() stopTimer() } diff --git a/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift b/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift index b179b4f9..33bf983e 100644 --- a/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift +++ b/Example/Authenticator/View Models/Authorizations/AuthorizationsViewModel.swift @@ -45,7 +45,7 @@ enum AuthorizationsViewModelState: Equatable { } } -class AuthorizationsViewModel { +final class AuthorizationsViewModel { private struct Images { static let noAuthorizations: UIImage = UIImage(named: "noAuthorizations", in: .authenticator_main, compatibleWith: nil)! static let noConnections: UIImage = UIImage(named: "noConnections", in: .authenticator_main, compatibleWith: nil)! diff --git a/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift b/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift index 9945b685..042810a1 100644 --- a/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift +++ b/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift @@ -22,6 +22,7 @@ import Foundation import SEAuthenticator +import SEAuthenticatorV2 import SEAuthenticatorCore protocol SingleAuthorizationViewModelEventsDelegate: class { @@ -31,22 +32,30 @@ protocol SingleAuthorizationViewModelEventsDelegate: class { final class SingleAuthorizationViewModel { private var connection: Connection? + private var authorizationId: String? + private var showLocationWarning: Bool = false private var detailViewModel: AuthorizationDetailViewModel? + private var poller: SEPoller? weak var delegate: SingleAuthorizationViewModelEventsDelegate? init(connectionId: String, authorizationId: String, locationManagement: LocationManagement) { guard let connection = ConnectionsCollector.with(id: connectionId) else { return } + self.authorizationId = authorizationId self.connection = connection - getAuthorization( - connection: connection, - authorizationId: authorizationId, - showLocationWarning: locationManagement.shouldShowLocationWarning(connection: connection) - ) + self.showLocationWarning = locationManagement.shouldShowLocationWarning(connection: connection) + + getAuthorization() + } + + deinit { + stopPolling() } - private func getAuthorization(connection: Connection, authorizationId: String, showLocationWarning: Bool) { + private func getAuthorization() { + guard let connection = connection, let authorizationId = authorizationId else { return } + AuthorizationsInteractor.refresh( connection: connection, authorizationId: authorizationId, @@ -64,7 +73,10 @@ final class SingleAuthorizationViewModel { guard let data = decryptedAuthorizationData else { return } DispatchQueue.main.async { - if let viewModel = AuthorizationDetailViewModel(data, apiVersion: connection.apiVersion) { + if strongSelf.detailViewModel != nil, + let dataV2 = data as? SEAuthorizationDataV2 { + strongSelf.updateDetailViewModel(status: dataV2.status) + } else if let viewModel = AuthorizationDetailViewModel(data, apiVersion: connection.apiVersion) { strongSelf.detailViewModel = viewModel strongSelf.detailViewModel?.delegate = self @@ -84,6 +96,28 @@ final class SingleAuthorizationViewModel { } } +// MARK: - Polling +extension SingleAuthorizationViewModel { + func stopPolling() { + poller?.stopPolling() + poller = nil + } + + private func setupPolling() { + if poller == nil { + poller = SEPoller(targetClass: self, selector: #selector(getEncryptedAuthorizationIfAvailable)) + getEncryptedAuthorizationIfAvailable() + poller?.startPolling() + } + } + + @objc private func getEncryptedAuthorizationIfAvailable() { + if poller != nil, connection != nil { + getAuthorization() + } + } +} + // MARK: - AuthorizationDetailEventsDelegate extension SingleAuthorizationViewModel: AuthorizationDetailEventsDelegate { func confirmPressed(_ authorizationId: String, apiVersion: ApiVersion) { @@ -106,17 +140,19 @@ extension SingleAuthorizationViewModel: AuthorizationDetailEventsDelegate { apiVersion: detailViewModel.apiVersion, data: confirmData, successV1: { - self.updateDetailViewModel(state: .confirmed) + self.updateDetailViewModel(status: .confirmed) }, successV2: { response in if response.status.isFinal { - self.updateDetailViewModel(state: .confirmed) + self.updateDetailViewModel(status: .confirmed) } }, failure: { _ in - self.updateDetailViewModel(state: .error) + self.updateDetailViewModel(status: .error) } ) + + setupPolling() } func denyPressed(_ authorizationId: String, apiVersion: ApiVersion) { @@ -139,17 +175,19 @@ extension SingleAuthorizationViewModel: AuthorizationDetailEventsDelegate { apiVersion: detailViewModel.apiVersion, data: confirmData, successV1: { - self.updateDetailViewModel(state: .denied) + self.updateDetailViewModel(status: .denied) }, successV2: { response in if response.status.isFinal { - self.updateDetailViewModel(state: .denied) + self.updateDetailViewModel(status: .denied) } }, failure: { _ in - self.updateDetailViewModel(state: .error) + self.updateDetailViewModel(status: .error) } ) + + setupPolling() } func authorizationExpired() { @@ -159,11 +197,17 @@ extension SingleAuthorizationViewModel: AuthorizationDetailEventsDelegate { } } - private func updateDetailViewModel(state: AuthorizationStateView.AuthorizationState) { + private func updateDetailViewModel(status: AuthorizationStatus) { + guard let state = AuthorizationStateView.AuthorizationState(rawValue: status.rawValue) else { return } + detailViewModel?.state.value = state detailViewModel?.actionTime = Date() - after(finalAuthorizationTimeToLive) { - self.delegate?.shouldClose() + + if status.isFinal { + stopPolling() + after(finalAuthorizationTimeToLive) { + self.delegate?.shouldClose() + } } } } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SESubmitActionResponseV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SESubmitActionResponseV2.swift index 9fb33284..0794bf69 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SESubmitActionResponseV2.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SESubmitActionResponseV2.swift @@ -39,9 +39,11 @@ public struct SESubmitActionResponseV2: SEBaseActionResponse { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let dataContainer = try container.nestedContainer(keyedBy: DataCodingKeys.self, forKey: .data) - let authorizationV2Id = try dataContainer.decodeIfPresent(Int.self, forKey: .authorizationId) - authorizationId = "\(authorizationV2Id)" - let connectionV2Id = try dataContainer.decodeIfPresent(Int.self, forKey: .connectionId) - connectionId = "\(connectionV2Id)" + if let authorizationV2Id = try dataContainer.decodeIfPresent(Int.self, forKey: .authorizationId) { + authorizationId = "\(authorizationV2Id)" + } + if let connectionV2Id = try dataContainer.decodeIfPresent(Int.self, forKey: .connectionId) { + connectionId = "\(connectionV2Id)" + } } } From e5a3850322e11ecfb25ffcbe507562e1db43cd8a Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Fri, 3 Jun 2022 10:46:27 +0300 Subject: [PATCH 090/110] fix encrypted data v2 parsing --- .../Classes/API/SEEncryptedData.swift | 10 ++++++---- .../Classes/Crypto/SECryptoHelper.swift | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/SaltedgeAuthenticatorCore/Classes/API/SEEncryptedData.swift b/SaltedgeAuthenticatorCore/Classes/API/SEEncryptedData.swift index 9d36d22d..d6062895 100644 --- a/SaltedgeAuthenticatorCore/Classes/API/SEEncryptedData.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/SEEncryptedData.swift @@ -44,13 +44,15 @@ public struct SEEncryptedData: SEBaseEncryptedAuthorizationData, Decodable, Equa data = try container.decode(String.self, forKey: .data) key = try container.decode(String.self, forKey: .key) iv = try container.decode(String.self, forKey: .iv) - if let connectionIdString = try container.decodeIfPresent(String.self, forKey: .connectionId) { - connectionId = connectionIdString - } else if let id = try container.decodeIfPresent(Int.self, forKey: .connectionId) { + if let id = try container.decodeIfPresent(Int.self, forKey: .connectionId) { // NOTE: connection_id in v2 is integer connectionId = "\(id)" + } else if let connectionIdString = try container.decodeIfPresent(String.self, forKey: .connectionId) { + connectionId = connectionIdString + } + if let id = try container.decodeIfPresent(Int.self, forKey: .id) { + entityId = "\(id)" } - entityId = try container.decodeIfPresent(String.self, forKey: .id) } public init(data: String, key: String, iv: String, connectionId: String? = nil) { diff --git a/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelper.swift b/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelper.swift index d217d9f1..9d5a74da 100644 --- a/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelper.swift +++ b/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelper.swift @@ -238,7 +238,7 @@ private struct AesCipher { do { let enc = try AES(key: keyArray, blockMode: CBC(iv: ivArray)).encrypt(data.bytes) let encData = NSData(bytes: enc, length: Int(enc.count)) - let base64String: String = encData.base64EncodedString(options: []) + let base64String: String = encData.base64EncodedString() return String(base64String) } catch { From 17865c53c67a98d2ed4dca98092c9c6503eac3bf Mon Sep 17 00:00:00 2001 From: vadimm Date: Fri, 3 Jun 2022 14:14:33 +0300 Subject: [PATCH 091/110] Fixed bug with term of conditions --- Example/Authenticator.xcodeproj/project.pbxproj | 4 ++++ Example/Authenticator/Utils/Helpers/AppSettings.swift | 4 ++-- Example/Tests/Supporting Files/application.example.plist | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Example/Authenticator.xcodeproj/project.pbxproj b/Example/Authenticator.xcodeproj/project.pbxproj index eea21ea0..829b67df 100644 --- a/Example/Authenticator.xcodeproj/project.pbxproj +++ b/Example/Authenticator.xcodeproj/project.pbxproj @@ -1701,6 +1701,7 @@ "${BUILT_PRODUCTS_DIR}/CryptoSwift/CryptoSwift.framework", "${BUILT_PRODUCTS_DIR}/FirebaseCore/FirebaseCore.framework", "${BUILT_PRODUCTS_DIR}/FirebaseCoreDiagnostics/FirebaseCoreDiagnostics.framework", + "${BUILT_PRODUCTS_DIR}/FirebaseCoreInternal/FirebaseCoreInternal.framework", "${BUILT_PRODUCTS_DIR}/FirebaseCrashlytics/FirebaseCrashlytics.framework", "${BUILT_PRODUCTS_DIR}/FirebaseInstallations/FirebaseInstallations.framework", "${BUILT_PRODUCTS_DIR}/GoogleDataTransport/GoogleDataTransport.framework", @@ -1723,6 +1724,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CryptoSwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseCore.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseCoreDiagnostics.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseCoreInternal.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseCrashlytics.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseInstallations.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleDataTransport.framework", @@ -1815,6 +1817,7 @@ "${BUILT_PRODUCTS_DIR}/CryptoSwift/CryptoSwift.framework", "${BUILT_PRODUCTS_DIR}/FirebaseCore/FirebaseCore.framework", "${BUILT_PRODUCTS_DIR}/FirebaseCoreDiagnostics/FirebaseCoreDiagnostics.framework", + "${BUILT_PRODUCTS_DIR}/FirebaseCoreInternal/FirebaseCoreInternal.framework", "${BUILT_PRODUCTS_DIR}/FirebaseCrashlytics/FirebaseCrashlytics.framework", "${BUILT_PRODUCTS_DIR}/FirebaseInstallations/FirebaseInstallations.framework", "${BUILT_PRODUCTS_DIR}/GoogleDataTransport/GoogleDataTransport.framework", @@ -1839,6 +1842,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CryptoSwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseCore.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseCoreDiagnostics.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseCoreInternal.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseCrashlytics.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseInstallations.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleDataTransport.framework", diff --git a/Example/Authenticator/Utils/Helpers/AppSettings.swift b/Example/Authenticator/Utils/Helpers/AppSettings.swift index fac6e90b..2effec92 100644 --- a/Example/Authenticator/Utils/Helpers/AppSettings.swift +++ b/Example/Authenticator/Utils/Helpers/AppSettings.swift @@ -27,14 +27,14 @@ enum WebURLResourceType: String { } enum EnvironmentSettingType: String { - case rootURL + case RootURL } class AppSettings { static private let kStagingEnvironment = "STAGING" static private let kProductionEnvironment = "PRODUCTION" - static var rootURL: URL { return URL(string: settingValueForType(.rootURL))! } + static var rootURL: URL { return URL(string: settingValueForType(.RootURL))! } // MARK: - Authenticator API static var termsURL: URL { return urlWithPathForType(.terms) } diff --git a/Example/Tests/Supporting Files/application.example.plist b/Example/Tests/Supporting Files/application.example.plist index 01562c42..84159fd8 100644 --- a/Example/Tests/Supporting Files/application.example.plist +++ b/Example/Tests/Supporting Files/application.example.plist @@ -6,14 +6,14 @@ pages/authenticator_terms PRODUCTION - rootURL + RootURL https://www.saltedge.com support_email authenticator@saltedge.com STAGING - rootURL + RootURL https://www.saltedge.com support_email authenticator@saltedge.com From d90c03d9386e104da227cb6813a97fbc7b9bf796 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Fri, 3 Jun 2022 14:22:10 +0300 Subject: [PATCH 092/110] fix key ib generation --- Example/Tests/Crypto/SECryptoHelperSpec.swift | 9 +++++ .../Classes/Crypto/SECryptoHelper.swift | 33 ++++++++++++------- .../Classes/Crypto/SECryptoHelperError.swift | 2 ++ 3 files changed, 33 insertions(+), 11 deletions(-) diff --git a/Example/Tests/Crypto/SECryptoHelperSpec.swift b/Example/Tests/Crypto/SECryptoHelperSpec.swift index 9033529f..2f012e37 100644 --- a/Example/Tests/Crypto/SECryptoHelperSpec.swift +++ b/Example/Tests/Crypto/SECryptoHelperSpec.swift @@ -44,5 +44,14 @@ class SECryptoHelperSpec: BaseSpec { expect(expectedDecryptedToken.json!["access_token"] as? String).to(equal("123456")) } } + + describe("generateRandomBytes") { + it("should correctly genrate non empty array of bytes") { + let iv = try! SECryptoHelper.generateRandomBytes(count: 16) + let isIvValid = [UInt8](iv).allSatisfy({ $0 != 0 }) + + expect(isIvValid).to(beTrue()) + } + } } } diff --git a/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelper.swift b/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelper.swift index 9d5a74da..d44d22fc 100644 --- a/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelper.swift +++ b/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelper.swift @@ -93,6 +93,9 @@ public struct SECryptoHelper { let encryptedKey = try publicEncrypt(data: key, keyForTag: tag) let encryptedIv = try publicEncrypt(data: iv, keyForTag: tag) + guard [UInt8](key).allSatisfy({ $0 != 0 }) else { throw SEAesCipherError.keyByteArrayIsNotValid } + guard [UInt8](iv).allSatisfy({ $0 != 0 }) else { throw SEAesCipherError.ivByteArrayIsNotValid } + return SEEncryptedData( data: try AesCipher.encrypt(message: message, key: key, iv: iv), key: encryptedKey.base64EncodedString(), @@ -141,6 +144,24 @@ public struct SECryptoHelper { return try SecKeyHelper.obtainKey(for: tag.privateTag) } + public static func generateRandomBytes(count: Int) throws -> Data { + var bytes: [Int8] = [Int8](repeating: 0, count: count) + + // Fill bytes with secure random data + let status = SecRandomCopyBytes( + kSecRandomDefault, + count, + &bytes + ) + + // A status of errSecSuccess indicates success + if status == errSecSuccess { + return Data(bytes.map(UInt8.init)) + } else { + throw SECryptoHelperError.errorGeneratingRandomBytes + } + } + // MARK: - Private Methods private static func privateDecrypt(message: String, privateKey: SecKey) throws -> Data { guard let data = Data(base64Encoded: message.replacingOccurrences(of: "\n", with: "")) else { @@ -213,17 +234,6 @@ public struct SECryptoHelper { return Data(bytes: uint8Pointer, count: encryptedDataBytes.count) } - - private static func generateRandomBytes(count: Int) throws -> Data { - let keyData = Data(count: count) - var newData = keyData - let result = newData.withUnsafeMutableBytes { SecRandomCopyBytes(kSecRandomDefault, keyData.count, $0.baseAddress!) } - if result == errSecSuccess { - return keyData - } else { - throw SECryptoHelperError.errorGeneratingRandomBytes - } - } } private struct AesCipher { @@ -256,6 +266,7 @@ private struct AesCipher { if keyArray.isEmpty { throw SEAesCipherError.noKeyProvided } if iv.isEmpty { throw SEAesCipherError.noIvProvided } + do { let dec = try AES(key: keyArray, blockMode: CBC(iv: ivArray)).decrypt(encryptedData.bytes) let decData = NSData(bytes: dec, length: Int(dec.count)) diff --git a/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelperError.swift b/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelperError.swift index 724e4b9e..aef42cf7 100644 --- a/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelperError.swift +++ b/SaltedgeAuthenticatorCore/Classes/Crypto/SECryptoHelperError.swift @@ -52,6 +52,8 @@ public enum SEAesCipherError: Error { case couldNotCreateDecodedString(fromData: Data) case noKeyProvided case noIvProvided + case ivByteArrayIsNotValid + case keyByteArrayIsNotValid var localizedDescription: String { var message = "" From 7e140c6350abe6253c78a402df2fd9df1aad074b Mon Sep 17 00:00:00 2001 From: vadimm Date: Fri, 3 Jun 2022 15:17:50 +0300 Subject: [PATCH 093/110] Changed the color for the pressed items in Settings and About screens. --- .../View Controllers/Settings/LicensesViewController.swift | 3 +++ Example/Authenticator/Views/Cells/SettingsCell.swift | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/Example/Authenticator/View Controllers/Settings/LicensesViewController.swift b/Example/Authenticator/View Controllers/Settings/LicensesViewController.swift index 31e02c52..504a7aee 100644 --- a/Example/Authenticator/View Controllers/Settings/LicensesViewController.swift +++ b/Example/Authenticator/View Controllers/Settings/LicensesViewController.swift @@ -75,6 +75,9 @@ extension LicensesViewController: UITableViewDataSource { ) cell.backgroundColor = .backgroundColor cell.textLabel?.text = viewModel.cellTitle(for: indexPath) + let bgColorView = UIView() + bgColorView.backgroundColor = .selectedColor + cell.selectedBackgroundView = bgColorView return cell } } diff --git a/Example/Authenticator/Views/Cells/SettingsCell.swift b/Example/Authenticator/Views/Cells/SettingsCell.swift index b417881b..6a007a58 100644 --- a/Example/Authenticator/Views/Cells/SettingsCell.swift +++ b/Example/Authenticator/Views/Cells/SettingsCell.swift @@ -26,6 +26,7 @@ final class SettingsCell: UITableViewCell, Dequeuable { override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: .value1, reuseIdentifier: reuseIdentifier) backgroundColor = .backgroundColor + selectedBackgroundView = setSelectedBackgroundView() textLabel?.textColor = .titleColor textLabel?.font = .auth_17regular detailTextLabel?.textColor = .titleColor @@ -44,6 +45,12 @@ final class SettingsCell: UITableViewCell, Dequeuable { default: break } } + + func setSelectedBackgroundView() -> UIView { + let bgColorView = UIView() + bgColorView.backgroundColor = .selectedColor + return bgColorView + } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") From 45e103376dd5d8de4288ddc054aca960b762e40b Mon Sep 17 00:00:00 2001 From: vadimm Date: Fri, 3 Jun 2022 15:21:32 +0300 Subject: [PATCH 094/110] Fixed logic in Serttings and About screen with reload data --- .../View Controllers/Settings/AboutViewController.swift | 9 --------- .../Settings/SettingsViewController.swift | 9 --------- 2 files changed, 18 deletions(-) diff --git a/Example/Authenticator/View Controllers/Settings/AboutViewController.swift b/Example/Authenticator/View Controllers/Settings/AboutViewController.swift index 830507f0..7c1c90f8 100644 --- a/Example/Authenticator/View Controllers/Settings/AboutViewController.swift +++ b/Example/Authenticator/View Controllers/Settings/AboutViewController.swift @@ -57,15 +57,6 @@ final class AboutViewController: BaseViewController { setupTableView() layout() } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - tableView.reloadData() - } - - func reloadData() { - tableView.reloadData() - } } // MARK: - Setup diff --git a/Example/Authenticator/View Controllers/Settings/SettingsViewController.swift b/Example/Authenticator/View Controllers/Settings/SettingsViewController.swift index 4e302802..e9327c43 100644 --- a/Example/Authenticator/View Controllers/Settings/SettingsViewController.swift +++ b/Example/Authenticator/View Controllers/Settings/SettingsViewController.swift @@ -42,15 +42,6 @@ final class SettingsViewController: BaseViewController { setupTableView() layout() } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - tableView.reloadData() - } - - func reloadData() { - tableView.reloadData() - } } // MARK: - Setup From c0cb868b195c828004bba7d30ee21d4be99f213c Mon Sep 17 00:00:00 2001 From: vadimm Date: Fri, 3 Jun 2022 15:50:27 +0300 Subject: [PATCH 095/110] =?UTF-8?q?=D0=A1hanged=20the=20navigation=20when?= =?UTF-8?q?=20choosing=20a=20language?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Coordinators/Settings/LanguagePickerCoordinator.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Example/Authenticator/Coordinators/Settings/LanguagePickerCoordinator.swift b/Example/Authenticator/Coordinators/Settings/LanguagePickerCoordinator.swift index 8b4ca1cb..b0820ee8 100644 --- a/Example/Authenticator/Coordinators/Settings/LanguagePickerCoordinator.swift +++ b/Example/Authenticator/Coordinators/Settings/LanguagePickerCoordinator.swift @@ -26,6 +26,7 @@ final class LanguagePickerCoordinator: Coordinator { private var rootViewController: UIViewController? private var currentViewController: LanguagePickerViewController private var viewModel = LanguagePickerViewModel() + private var settingsCoordinator: SettingsCoordinator? init(rootViewController: UIViewController) { self.rootViewController = rootViewController @@ -45,6 +46,7 @@ final class LanguagePickerCoordinator: Coordinator { // MARK: - LanguagePickerEventsDelegate extension LanguagePickerCoordinator: LanguagePickerEventsDelegate { func languageSelected() { - rootViewController?.navigationController?.popToRootViewController(animated: true) + settingsCoordinator = SettingsCoordinator(rootController: currentViewController) + settingsCoordinator?.start() } } From 7257264aad7cb5dce1a613e44b2d07ffe5712e69 Mon Sep 17 00:00:00 2001 From: vadimm Date: Fri, 3 Jun 2022 17:03:26 +0300 Subject: [PATCH 096/110] Changed navigation in Language screen --- .../Coordinators/Settings/LanguagePickerCoordinator.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Example/Authenticator/Coordinators/Settings/LanguagePickerCoordinator.swift b/Example/Authenticator/Coordinators/Settings/LanguagePickerCoordinator.swift index b0820ee8..151579de 100644 --- a/Example/Authenticator/Coordinators/Settings/LanguagePickerCoordinator.swift +++ b/Example/Authenticator/Coordinators/Settings/LanguagePickerCoordinator.swift @@ -26,7 +26,6 @@ final class LanguagePickerCoordinator: Coordinator { private var rootViewController: UIViewController? private var currentViewController: LanguagePickerViewController private var viewModel = LanguagePickerViewModel() - private var settingsCoordinator: SettingsCoordinator? init(rootViewController: UIViewController) { self.rootViewController = rootViewController @@ -46,7 +45,6 @@ final class LanguagePickerCoordinator: Coordinator { // MARK: - LanguagePickerEventsDelegate extension LanguagePickerCoordinator: LanguagePickerEventsDelegate { func languageSelected() { - settingsCoordinator = SettingsCoordinator(rootController: currentViewController) - settingsCoordinator?.start() + rootViewController?.navigationController?.popViewController(animated: true) } } From 4a8f5ad4b9d5ef6d7a63a9d3374a53979f9a4397 Mon Sep 17 00:00:00 2001 From: vadimm Date: Mon, 6 Jun 2022 17:29:51 +0300 Subject: [PATCH 097/110] Fixed bug with displaying logo for Connection, Instant Action and Authorizations screen --- Example/Authenticator/Models/Database/Connection.swift | 2 +- .../Classes/API/Responses/SEProviderResponseV2.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Example/Authenticator/Models/Database/Connection.swift b/Example/Authenticator/Models/Database/Connection.swift index b2663a25..75b11fa9 100644 --- a/Example/Authenticator/Models/Database/Connection.swift +++ b/Example/Authenticator/Models/Database/Connection.swift @@ -55,7 +55,7 @@ enum ConnectionStatus: String { case name case code case baseUrlString = "connect_url" - case logoUrlString = "provier_logo_url" + case logoUrlString = "provider_logo_url" case accessToken = "access_token" case status case supportEmail = "support_email" diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponseV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponseV2.swift index 1e0a24f1..a9a69546 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponseV2.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SEProviderResponseV2.swift @@ -40,7 +40,7 @@ public struct SEProviderResponseV2: Decodable { enum DataCodingKeys: String, CodingKey { case name = "provider_name" case baseUrl = "sca_service_url" - case logoUrl = "logo_url" + case logoUrl = "provider_logo_url" case apiVersion = "api_version" case supportEmail = "provider_support_email" case providerId = "provider_id" From e1a4d0fb1cffe7c4e4e29c3555fe19603201933f Mon Sep 17 00:00:00 2001 From: vadimm Date: Fri, 17 Jun 2022 16:53:42 +0300 Subject: [PATCH 098/110] Fix bug with shadow in Authorization headers --- .../Views/Components/RoundedShadowView.swift | 45 ++++++++----------- 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/Example/Authenticator/Views/Components/RoundedShadowView.swift b/Example/Authenticator/Views/Components/RoundedShadowView.swift index 1cdee312..ce6f3c39 100644 --- a/Example/Authenticator/Views/Components/RoundedShadowView.swift +++ b/Example/Authenticator/Views/Components/RoundedShadowView.swift @@ -23,44 +23,35 @@ import UIKit class RoundedShadowView: UIView { - private var shadowLayer: CAShapeLayer! - private var cornerRadius: CGFloat - + init(cornerRadius: CGFloat) { - self.cornerRadius = cornerRadius super.init(frame: .zero) + layer.cornerRadius = cornerRadius + backgroundColor = UIColor.secondaryBackground } - override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { - super.traitCollectionDidChange(previousTraitCollection) - - if #available(iOS 13.0, *), - traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection), - let shadowLayer = shadowLayer { - shadowLayer.fillColor = UIColor.secondaryBackground.cgColor - } + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() - if shadowLayer == nil { - shadowLayer = CAShapeLayer() - shadowLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius).cgPath - shadowLayer.fillColor = UIColor.secondaryBackground.cgColor + layer.shadowColor = UIColor(red: 0.056, green: 0.126, blue: 0.179, alpha: 0.12).cgColor + layer.shadowPath = UIBezierPath(rect: bounds).cgPath + layer.shadowOffset = .zero + layer.shadowOpacity = 0.7 + layer.shadowRadius = 8 + } - shadowLayer.shadowColor = UIColor(red: 0.056, green: 0.126, blue: 0.179, alpha: 0.12).cgColor - shadowLayer.shadowPath = shadowLayer.path - shadowLayer.shadowOffset = .zero - shadowLayer.shadowOpacity = 0.7 - shadowLayer.shadowRadius = 8 - shadowLayer.shadowPath = UIBezierPath(rect: bounds).cgPath + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) - layer.insertSublayer(shadowLayer, at: 0) + if #available(iOS 13.0, *), + traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) { + layer.shadowColor = UIColor.secondaryBackground.cgColor } } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } } + + From 52a013b68c1072e200538dce325a1fb09d1cfc0a Mon Sep 17 00:00:00 2001 From: vadimm Date: Tue, 21 Jun 2022 12:17:33 +0300 Subject: [PATCH 099/110] Removed redundant prefix, added private modificator for selectedBackgroundView --- Example/Authenticator/Views/Cells/SettingsCell.swift | 2 +- Example/Authenticator/Views/Components/RoundedShadowView.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Example/Authenticator/Views/Cells/SettingsCell.swift b/Example/Authenticator/Views/Cells/SettingsCell.swift index 6a007a58..abdba730 100644 --- a/Example/Authenticator/Views/Cells/SettingsCell.swift +++ b/Example/Authenticator/Views/Cells/SettingsCell.swift @@ -46,7 +46,7 @@ final class SettingsCell: UITableViewCell, Dequeuable { } } - func setSelectedBackgroundView() -> UIView { + private func setSelectedBackgroundView() -> UIView { let bgColorView = UIView() bgColorView.backgroundColor = .selectedColor return bgColorView diff --git a/Example/Authenticator/Views/Components/RoundedShadowView.swift b/Example/Authenticator/Views/Components/RoundedShadowView.swift index ce6f3c39..a8b42e2b 100644 --- a/Example/Authenticator/Views/Components/RoundedShadowView.swift +++ b/Example/Authenticator/Views/Components/RoundedShadowView.swift @@ -27,7 +27,7 @@ class RoundedShadowView: UIView { init(cornerRadius: CGFloat) { super.init(frame: .zero) layer.cornerRadius = cornerRadius - backgroundColor = UIColor.secondaryBackground + backgroundColor = .secondaryBackground } required init?(coder: NSCoder) { From bc9dd84df73b24f0ec195a17d2166f8656d8e7d0 Mon Sep 17 00:00:00 2001 From: vadimm Date: Tue, 21 Jun 2022 12:31:46 +0300 Subject: [PATCH 100/110] Changed func to variable --- Example/Authenticator/Views/Cells/SettingsCell.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Example/Authenticator/Views/Cells/SettingsCell.swift b/Example/Authenticator/Views/Cells/SettingsCell.swift index abdba730..82159ee3 100644 --- a/Example/Authenticator/Views/Cells/SettingsCell.swift +++ b/Example/Authenticator/Views/Cells/SettingsCell.swift @@ -26,7 +26,7 @@ final class SettingsCell: UITableViewCell, Dequeuable { override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: .value1, reuseIdentifier: reuseIdentifier) backgroundColor = .backgroundColor - selectedBackgroundView = setSelectedBackgroundView() + selectedBackgroundView = selectedBackgroundСolorView textLabel?.textColor = .titleColor textLabel?.font = .auth_17regular detailTextLabel?.textColor = .titleColor @@ -46,7 +46,7 @@ final class SettingsCell: UITableViewCell, Dequeuable { } } - private func setSelectedBackgroundView() -> UIView { + private var selectedBackgroundСolorView: UIView { let bgColorView = UIView() bgColorView.backgroundColor = .selectedColor return bgColorView From 591787ae87a0d8174299f72feea0f40c15c8498a Mon Sep 17 00:00:00 2001 From: vadimm Date: Mon, 27 Jun 2022 23:12:20 +0300 Subject: [PATCH 101/110] Fixed authorizations parse from v1 on v2 branch --- Example/Podfile.lock | 92 +++++++++---------- .../Classes/API/SEEncryptedData.swift | 13 ++- 2 files changed, 52 insertions(+), 53 deletions(-) diff --git a/Example/Podfile.lock b/Example/Podfile.lock index 80e61092..c010b55b 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -1,77 +1,77 @@ PODS: - CryptoSwift (1.5.1) - - Firebase/Analytics (9.0.0): + - Firebase/Analytics (9.2.0): - Firebase/Core - - Firebase/Core (9.0.0): + - Firebase/Core (9.2.0): - Firebase/CoreOnly - - FirebaseAnalytics (~> 9.0.0) - - Firebase/CoreOnly (9.0.0): - - FirebaseCore (= 9.0.0) - - Firebase/Crashlytics (9.0.0): + - FirebaseAnalytics (~> 9.2.0) + - Firebase/CoreOnly (9.2.0): + - FirebaseCore (= 9.2.0) + - Firebase/Crashlytics (9.2.0): - Firebase/CoreOnly - - FirebaseCrashlytics (~> 9.0.0) - - FirebaseAnalytics (9.0.0): - - FirebaseAnalytics/AdIdSupport (= 9.0.0) + - FirebaseCrashlytics (~> 9.2.0) + - FirebaseAnalytics (9.2.0): + - FirebaseAnalytics/AdIdSupport (= 9.2.0) - FirebaseCore (~> 9.0) - FirebaseInstallations (~> 9.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.7) - GoogleUtilities/MethodSwizzler (~> 7.7) - GoogleUtilities/Network (~> 7.7) - "GoogleUtilities/NSData+zlib (~> 7.7)" - - nanopb (~> 2.30908.0) - - FirebaseAnalytics/AdIdSupport (9.0.0): + - nanopb (< 2.30910.0, >= 2.30908.0) + - FirebaseAnalytics/AdIdSupport (9.2.0): - FirebaseCore (~> 9.0) - FirebaseInstallations (~> 9.0) - - GoogleAppMeasurement (= 9.0.0) + - GoogleAppMeasurement (= 9.2.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.7) - GoogleUtilities/MethodSwizzler (~> 7.7) - GoogleUtilities/Network (~> 7.7) - "GoogleUtilities/NSData+zlib (~> 7.7)" - - nanopb (~> 2.30908.0) - - FirebaseCore (9.0.0): + - nanopb (< 2.30910.0, >= 2.30908.0) + - FirebaseCore (9.2.0): - FirebaseCoreDiagnostics (~> 9.0) - FirebaseCoreInternal (~> 9.0) - GoogleUtilities/Environment (~> 7.7) - GoogleUtilities/Logger (~> 7.7) - - FirebaseCoreDiagnostics (9.0.0): - - GoogleDataTransport (~> 9.1) + - FirebaseCoreDiagnostics (9.2.0): + - GoogleDataTransport (< 10.0.0, >= 9.1.4) - GoogleUtilities/Environment (~> 7.7) - GoogleUtilities/Logger (~> 7.7) - - nanopb (~> 2.30908.0) - - FirebaseCoreInternal (9.0.0): + - nanopb (< 2.30910.0, >= 2.30908.0) + - FirebaseCoreInternal (9.2.0): - "GoogleUtilities/NSData+zlib (~> 7.7)" - - FirebaseCrashlytics (9.0.0): + - FirebaseCrashlytics (9.2.0): - FirebaseCore (~> 9.0) - FirebaseInstallations (~> 9.0) - - GoogleDataTransport (~> 9.1) + - GoogleDataTransport (< 10.0.0, >= 9.1.4) - GoogleUtilities/Environment (~> 7.7) - - nanopb (~> 2.30908.0) + - nanopb (< 2.30910.0, >= 2.30908.0) - PromisesObjC (~> 2.1) - - FirebaseInstallations (9.0.0): + - FirebaseInstallations (9.2.0): - FirebaseCore (~> 9.0) - GoogleUtilities/Environment (~> 7.7) - GoogleUtilities/UserDefaults (~> 7.7) - PromisesObjC (~> 2.1) - - GoogleAppMeasurement (9.0.0): - - GoogleAppMeasurement/AdIdSupport (= 9.0.0) + - GoogleAppMeasurement (9.2.0): + - GoogleAppMeasurement/AdIdSupport (= 9.2.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.7) - GoogleUtilities/MethodSwizzler (~> 7.7) - GoogleUtilities/Network (~> 7.7) - "GoogleUtilities/NSData+zlib (~> 7.7)" - - nanopb (~> 2.30908.0) - - GoogleAppMeasurement/AdIdSupport (9.0.0): - - GoogleAppMeasurement/WithoutAdIdSupport (= 9.0.0) + - nanopb (< 2.30910.0, >= 2.30908.0) + - GoogleAppMeasurement/AdIdSupport (9.2.0): + - GoogleAppMeasurement/WithoutAdIdSupport (= 9.2.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.7) - GoogleUtilities/MethodSwizzler (~> 7.7) - GoogleUtilities/Network (~> 7.7) - "GoogleUtilities/NSData+zlib (~> 7.7)" - - nanopb (~> 2.30908.0) - - GoogleAppMeasurement/WithoutAdIdSupport (9.0.0): + - nanopb (< 2.30910.0, >= 2.30908.0) + - GoogleAppMeasurement/WithoutAdIdSupport (9.2.0): - GoogleUtilities/AppDelegateSwizzler (~> 7.7) - GoogleUtilities/MethodSwizzler (~> 7.7) - GoogleUtilities/Network (~> 7.7) - "GoogleUtilities/NSData+zlib (~> 7.7)" - - nanopb (~> 2.30908.0) + - nanopb (< 2.30910.0, >= 2.30908.0) - GoogleDataTransport (9.1.4): - GoogleUtilities/Environment (~> 7.7) - nanopb (< 2.30910.0, >= 2.30908.0) @@ -96,13 +96,13 @@ PODS: - GoogleUtilities/UserDefaults (7.7.0): - GoogleUtilities/Logger - JOSESwift (2.4.0) - - nanopb (2.30908.0): - - nanopb/decode (= 2.30908.0) - - nanopb/encode (= 2.30908.0) - - nanopb/decode (2.30908.0) - - nanopb/encode (2.30908.0) + - nanopb (2.30909.0): + - nanopb/decode (= 2.30909.0) + - nanopb/encode (= 2.30909.0) + - nanopb/decode (2.30909.0) + - nanopb/encode (2.30909.0) - Nimble (10.0.0) - - PromisesObjC (2.1.0) + - PromisesObjC (2.1.1) - Quick (5.0.1) - ReachabilitySwift (5.0.0) - Realm (5.5.1): @@ -173,20 +173,20 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: CryptoSwift: c4f2debceb38bf44c80659afe009f71e23e4a082 - Firebase: a876fadc5ea653a377693376fd4f885c62704512 - FirebaseAnalytics: ea4f6f4b604a20b4de4d3c7f7a2cb51d9a989040 - FirebaseCore: e4c0b5d9727eaee0b43f9ed00baff7500c188d7b - FirebaseCoreDiagnostics: 54410e5d156bf406a764c2722d9f77d682723b4c - FirebaseCoreInternal: 5b8f4f2e2970a4cb9bd1cf7ada16c8ba69a29530 - FirebaseCrashlytics: 08561398868790fc9694b85efb4c080940926fcc - FirebaseInstallations: e693c0dfe404af44afbd553de42498b2ca1ca189 - GoogleAppMeasurement: 2c2792d43ebdea0524adbc90cba9139721f3039b + Firebase: 4ba896cb8e5105d4b9e247e1c1b6222b548df55a + FirebaseAnalytics: af5a03a8dff7648c7b8486f6a78b1368e0268dd3 + FirebaseCore: 0e27f2a15d8f7b7ef11e7d93e23b1cbab55d748c + FirebaseCoreDiagnostics: ad3f6c68b7c5b63b7cf15b0785d7137f05f32268 + FirebaseCoreInternal: cb966328b6985dbd6f535e1461291063e1c4a00f + FirebaseCrashlytics: 9fff819edb2bfc9d3eff612225b207d41945a935 + FirebaseInstallations: 21186f0ca7849f90f4a3219fa31a5eca2e30f113 + GoogleAppMeasurement: 7a33224321f975d58c166657260526775d9c6b1a GoogleDataTransport: 5fffe35792f8b96ec8d6775f5eccd83c998d5a3b GoogleUtilities: e0913149f6b0625b553d70dae12b49fc62914fd1 JOSESwift: 7ff178bb9173ff42c6e990929a9f2fa702a34f69 - nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96 + nanopb: b552cce312b6c8484180ef47159bc0f65a1f0431 Nimble: 5316ef81a170ce87baf72dd961f22f89a602ff84 - PromisesObjC: 99b6f43f9e1044bd87a95a60beff28c2c44ddb72 + PromisesObjC: ab77feca74fa2823e7af4249b8326368e61014cb Quick: 749aa754fd1e7d984f2000fe051e18a3a9809179 ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 Realm: c2ffe0667f9c98c9ba65f608ba85684d39826145 diff --git a/SaltedgeAuthenticatorCore/Classes/API/SEEncryptedData.swift b/SaltedgeAuthenticatorCore/Classes/API/SEEncryptedData.swift index d6062895..1bc79b94 100644 --- a/SaltedgeAuthenticatorCore/Classes/API/SEEncryptedData.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/SEEncryptedData.swift @@ -44,15 +44,14 @@ public struct SEEncryptedData: SEBaseEncryptedAuthorizationData, Decodable, Equa data = try container.decode(String.self, forKey: .data) key = try container.decode(String.self, forKey: .key) iv = try container.decode(String.self, forKey: .iv) - if let id = try container.decodeIfPresent(Int.self, forKey: .connectionId) { - // NOTE: connection_id in v2 is integer - connectionId = "\(id)" - } else if let connectionIdString = try container.decodeIfPresent(String.self, forKey: .connectionId) { + + if let connectionIdString = try container.decodeIfPresent(String.self, forKey: .connectionId) { connectionId = connectionIdString + } else if let id = try container.decodeIfPresent(Int.self, forKey: .connectionId) { + connectionId = "\(id)" } - if let id = try container.decodeIfPresent(Int.self, forKey: .id) { - entityId = "\(id)" - } + + entityId = try container.decodeIfPresent(String.self, forKey: .id) } public init(data: String, key: String, iv: String, connectionId: String? = nil) { From 70d0a309403d82db1eb57d8254ab71d9863fc398 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Mon, 1 Aug 2022 10:13:56 +0300 Subject: [PATCH 102/110] Changed timeout interval to 20 seconds --- .../Classes/API/Networking/URLSessionManager.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SaltedgeAuthenticatorCore/Classes/API/Networking/URLSessionManager.swift b/SaltedgeAuthenticatorCore/Classes/API/Networking/URLSessionManager.swift index 309d3ea4..11a8f380 100644 --- a/SaltedgeAuthenticatorCore/Classes/API/Networking/URLSessionManager.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/Networking/URLSessionManager.swift @@ -38,7 +38,7 @@ struct URLSessionManager { static func createSession() -> URLSession { let config: URLSessionConfiguration = .default - config.timeoutIntervalForRequest = 10.0 + config.timeoutIntervalForRequest = 20.0 config.timeoutIntervalForResource = 30.0 config.requestCachePolicy = .reloadIgnoringLocalCacheData From 9bfe451a24df9012b2e1290d8b21d3fcb9012942 Mon Sep 17 00:00:00 2001 From: vadimm Date: Wed, 24 Aug 2022 14:46:16 +0300 Subject: [PATCH 103/110] Don't allow web view to open hyperlinks --- .../Common/WKWebViewController.swift | 8 ++++++++ .../Authorizations/AuthorizationContentView.swift | 13 +++++++++++++ 2 files changed, 21 insertions(+) diff --git a/Example/Authenticator/View Controllers/Common/WKWebViewController.swift b/Example/Authenticator/View Controllers/Common/WKWebViewController.swift index bcf1b75f..95629b8d 100644 --- a/Example/Authenticator/View Controllers/Common/WKWebViewController.swift +++ b/Example/Authenticator/View Controllers/Common/WKWebViewController.swift @@ -106,6 +106,14 @@ extension WKWebViewController: WKNavigationDelegate { loadingIndicator.stop() handleError(error as NSError) } + + func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { + if navigationAction.navigationType == .linkActivated { + decisionHandler(.cancel) + } else { + decisionHandler(.allow) + } + } #if DEBUG func webView(_ webView: WKWebView, diff --git a/Example/Authenticator/Views/Authorizations/AuthorizationContentView.swift b/Example/Authenticator/Views/Authorizations/AuthorizationContentView.swift index 604d7f85..c84bed5d 100644 --- a/Example/Authenticator/Views/Authorizations/AuthorizationContentView.swift +++ b/Example/Authenticator/Views/Authorizations/AuthorizationContentView.swift @@ -156,7 +156,9 @@ private extension AuthorizationContentView { let supportDarkCSS = "" contentStackView.removeAllArrangedSubviews() + webView.navigationDelegate = self webView.loadHTMLString(content + supportDarkCSS, baseURL: nil) + contentStackView.addArrangedSubview(webView) } } @@ -197,3 +199,14 @@ extension AuthorizationContentView: Layoutable { stateView.edgesToSuperview() } } + + +extension AuthorizationContentView: WKNavigationDelegate { + func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { + if navigationAction.navigationType == .linkActivated { + decisionHandler(.cancel) + } else { + decisionHandler(.allow) + } + } +} From 0d983a62a1bc30ec5292b089c370a1bdfa8ee2e6 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Wed, 21 Sep 2022 17:15:15 +0300 Subject: [PATCH 104/110] disable links openning in content view and disabled js --- .../Authenticator.xcodeproj/project.pbxproj | 6 +++ .../Common/WKWebViewController.swift | 4 +- .../AuthorizationContentView.swift | 20 +------ .../Views/Components/ContentWebView.swift | 53 +++++++++++++++++++ 4 files changed, 62 insertions(+), 21 deletions(-) create mode 100644 Example/Authenticator/Views/Components/ContentWebView.swift diff --git a/Example/Authenticator.xcodeproj/project.pbxproj b/Example/Authenticator.xcodeproj/project.pbxproj index 829b67df..bd53fb9d 100644 --- a/Example/Authenticator.xcodeproj/project.pbxproj +++ b/Example/Authenticator.xcodeproj/project.pbxproj @@ -8,6 +8,8 @@ /* Begin PBXBuildFile section */ 0479C5522387F8C300F3FE0A /* AVCaptureHelperSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0479C5512387F8C300F3FE0A /* AVCaptureHelperSpec.swift */; }; + 5D5C90DF28DB510000D11FDD /* ContentWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D5C90DE28DB510000D11FDD /* ContentWebView.swift */; }; + 5D5C90E028DB510000D11FDD /* ContentWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D5C90DE28DB510000D11FDD /* ContentWebView.swift */; }; 5DF91D13283516B300B80A9B /* SECryptoHelperSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DF91D12283516B300B80A9B /* SECryptoHelperSpec.swift */; }; 6CF5A0163F3E926E5B271FBE /* Pods_Authenticator_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC7966F04DE3EFEB4C47030C /* Pods_Authenticator_Tests.framework */; }; 8432878826CE378400F77DD5 /* AuthorizationStatusSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8432878726CE378400F77DD5 /* AuthorizationStatusSpec.swift */; }; @@ -399,6 +401,7 @@ 1249D71A41FFB9C79FF0AE5E /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 5A77B827BD85D2BAD4D1123E /* Pods-Authenticator_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Authenticator_Tests.debug.xcconfig"; path = "Target Support Files/Pods-Authenticator_Tests/Pods-Authenticator_Tests.debug.xcconfig"; sourceTree = ""; }; 5C3EEC2ACBD5A3E62C348862 /* Pods-Authenticator_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Authenticator_Example.release.xcconfig"; path = "Target Support Files/Pods-Authenticator_Example/Pods-Authenticator_Example.release.xcconfig"; sourceTree = ""; }; + 5D5C90DE28DB510000D11FDD /* ContentWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentWebView.swift; sourceTree = ""; }; 5DF91D12283516B300B80A9B /* SECryptoHelperSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SECryptoHelperSpec.swift; sourceTree = ""; }; 607FACD01AFB9204008FA782 /* Authenticator_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Authenticator_Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 607FACE51AFB9204008FA782 /* Authenticator_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Authenticator_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1262,6 +1265,7 @@ 9DCED86422CE18270050ED3C /* LoadingIndicator.swift */, 9D5BCFE2248108F1009A6B40 /* AspectFitImageView.swift */, 9DF7964224978A2F001290DA /* RoundedShadowView.swift */, + 5D5C90DE28DB510000D11FDD /* ContentWebView.swift */, ); path = Components; sourceTree = ""; @@ -1909,6 +1913,7 @@ 9581A9DA2477DFE40066EF5E /* SettingsCoordinator.swift in Sources */, 9DCED8A922CE18280050ED3C /* OnboardingViewController.swift in Sources */, 9DCED8CC22CE18280050ED3C /* ViewsExtensions.swift in Sources */, + 5D5C90DF28DB510000D11FDD /* ContentWebView.swift in Sources */, 9D87F49E2429326800F85D1F /* ConnectionCell.swift in Sources */, 9DCED8EC22CE18280050ED3C /* BiometricsHelper.swift in Sources */, 9DCED90522CE18280050ED3C /* PasscodeView.swift in Sources */, @@ -2175,6 +2180,7 @@ 9DF7964424978A2F001290DA /* RoundedShadowView.swift in Sources */, 9DE324D922D39AB900EB162A /* CustomButton.swift in Sources */, 9DE324B422D39A3A00EB162A /* BiometricsHelper.swift in Sources */, + 5D5C90E028DB510000D11FDD /* ContentWebView.swift in Sources */, 9DD4DF262375969B000A9B80 /* QuickActionsHelper.swift in Sources */, 9DE324DE22D39AC500EB162A /* PasscodeView.swift in Sources */, 9DE3249722D399E800EB162A /* ViewsExtensions.swift in Sources */, diff --git a/Example/Authenticator/View Controllers/Common/WKWebViewController.swift b/Example/Authenticator/View Controllers/Common/WKWebViewController.swift index 95629b8d..a60bf0c4 100644 --- a/Example/Authenticator/View Controllers/Common/WKWebViewController.swift +++ b/Example/Authenticator/View Controllers/Common/WKWebViewController.swift @@ -24,11 +24,11 @@ import UIKit import WebKit import TinyConstraints -protocol WKWebViewControllerDelegate: class { +protocol WKWebViewControllerDelegate: AnyObject { func showError(_ error: String) } -class WKWebViewController: BaseViewController { +final class WKWebViewController: BaseViewController { weak var delegate: WKWebViewControllerDelegate? var messageBar: MessageBarView? var displayType: Presentation = .modal diff --git a/Example/Authenticator/Views/Authorizations/AuthorizationContentView.swift b/Example/Authenticator/Views/Authorizations/AuthorizationContentView.swift index c84bed5d..3c781723 100644 --- a/Example/Authenticator/Views/Authorizations/AuthorizationContentView.swift +++ b/Example/Authenticator/Views/Authorizations/AuthorizationContentView.swift @@ -21,7 +21,6 @@ // import UIKit -import WebKit private struct Layout { static let sideOffset: CGFloat = 16.0 @@ -45,12 +44,7 @@ final class AuthorizationContentView: UIView { private lazy var descriptionTextView = UITextView() private lazy var attributesStackView = AuthorizationContentDynamicStackView() - private lazy var webView: WKWebView = { - let webView = WKWebView(frame: .zero, configuration: WKWebViewConfiguration()) - webView.layer.masksToBounds = true - webView.layer.cornerRadius = 4.0 - return webView - }() + private lazy var webView = ContentWebView() private var contentStackView: UIStackView = { let stackView = UIStackView() stackView.axis = .vertical @@ -156,7 +150,6 @@ private extension AuthorizationContentView { let supportDarkCSS = "" contentStackView.removeAllArrangedSubviews() - webView.navigationDelegate = self webView.loadHTMLString(content + supportDarkCSS, baseURL: nil) contentStackView.addArrangedSubview(webView) @@ -199,14 +192,3 @@ extension AuthorizationContentView: Layoutable { stateView.edgesToSuperview() } } - - -extension AuthorizationContentView: WKNavigationDelegate { - func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { - if navigationAction.navigationType == .linkActivated { - decisionHandler(.cancel) - } else { - decisionHandler(.allow) - } - } -} diff --git a/Example/Authenticator/Views/Components/ContentWebView.swift b/Example/Authenticator/Views/Components/ContentWebView.swift new file mode 100644 index 00000000..925b16a1 --- /dev/null +++ b/Example/Authenticator/Views/Components/ContentWebView.swift @@ -0,0 +1,53 @@ +// +// ContentWebView +// This file is part of the Salt Edge Authenticator distribution +// (https://github.com/saltedge/sca-authenticator-ios) +// Copyright © 2022 Salt Edge Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 or later. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// For the additional permissions granted for Salt Edge Authenticator +// under Section 7 of the GNU General Public License see THIRD_PARTY_NOTICES.md +// + +import WebKit + +final class ContentWebView: WKWebView { + init() { + let preferences = WKPreferences() + preferences.javaScriptEnabled = false + let configuration = WKWebViewConfiguration() + configuration.preferences = preferences + + super.init(frame: .zero, configuration: configuration) + allowsLinkPreview = false + navigationDelegate = self + layer.masksToBounds = true + layer.cornerRadius = 4.0 + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +// MARK: - WKNavigationDelegate +extension ContentWebView: WKNavigationDelegate { + func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { + if navigationAction.navigationType == .linkActivated { + decisionHandler(.cancel) + } else { + decisionHandler(.allow) + } + } +} From 020e783cffbde25e0f12ec52d9fd2091ec1d6d00 Mon Sep 17 00:00:00 2001 From: vadimm Date: Wed, 12 Oct 2022 13:55:35 +0300 Subject: [PATCH 105/110] Final changes of testing v2 --- Example/Authenticator/Base.lproj/LaunchScreen.storyboard | 6 +++--- .../Supporting Files/application.example.plist | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Example/Authenticator/Base.lproj/LaunchScreen.storyboard b/Example/Authenticator/Base.lproj/LaunchScreen.storyboard index 05c70f55..c16ada7c 100644 --- a/Example/Authenticator/Base.lproj/LaunchScreen.storyboard +++ b/Example/Authenticator/Base.lproj/LaunchScreen.storyboard @@ -1,9 +1,9 @@ - + - + @@ -21,7 +21,7 @@ - + diff --git a/Example/Authenticator/Supporting Files/application.example.plist b/Example/Authenticator/Supporting Files/application.example.plist index 01562c42..84159fd8 100644 --- a/Example/Authenticator/Supporting Files/application.example.plist +++ b/Example/Authenticator/Supporting Files/application.example.plist @@ -6,14 +6,14 @@ pages/authenticator_terms PRODUCTION - rootURL + RootURL https://www.saltedge.com support_email authenticator@saltedge.com STAGING - rootURL + RootURL https://www.saltedge.com support_email authenticator@saltedge.com From 9540d172e090ea5989f458124fe9512db1d616ca Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Fri, 4 Nov 2022 17:16:36 +0200 Subject: [PATCH 106/110] updated project version to 4.0 --- Example/Authenticator.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Example/Authenticator.xcodeproj/project.pbxproj b/Example/Authenticator.xcodeproj/project.pbxproj index 3b818949..2afa2591 100644 --- a/Example/Authenticator.xcodeproj/project.pbxproj +++ b/Example/Authenticator.xcodeproj/project.pbxproj @@ -2387,7 +2387,7 @@ CODE_SIGN_ENTITLEMENTS = Authenticator_Example.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 141; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; EXCLUDED_ARCHS = ""; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; @@ -2395,7 +2395,7 @@ INFOPLIST_FILE = "Authenticator/Supporting Files/info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 3.3.1; + MARKETING_VERSION = 4.0; MODULE_NAME = ExampleApp; PRODUCT_BUNDLE_IDENTIFIER = com.saltedge.authenticator; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2414,7 +2414,7 @@ CODE_SIGN_ENTITLEMENTS = Authenticator_Example.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 141; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; EXCLUDED_ARCHS = ""; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; @@ -2422,7 +2422,7 @@ INFOPLIST_FILE = "Authenticator/Supporting Files/info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 3.3.1; + MARKETING_VERSION = 4.0; MODULE_NAME = ExampleApp; PRODUCT_BUNDLE_IDENTIFIER = com.saltedge.authenticator; PRODUCT_NAME = "$(TARGET_NAME)"; From af3a786de57de46bf933cb45a87d24a232f1c574 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Fri, 25 Nov 2022 12:55:21 +0200 Subject: [PATCH 107/110] added v2 readme --- SaltedgeAuthenticatorSDKv2/README.md | 482 ++++++++++++++++++ .../SaltedgeAuthenticatorSDKv2.podspec | 1 - 2 files changed, 482 insertions(+), 1 deletion(-) create mode 100644 SaltedgeAuthenticatorSDKv2/README.md diff --git a/SaltedgeAuthenticatorSDKv2/README.md b/SaltedgeAuthenticatorSDKv2/README.md new file mode 100644 index 00000000..8b7474c4 --- /dev/null +++ b/SaltedgeAuthenticatorSDKv2/README.md @@ -0,0 +1,482 @@ +# Authenticator iOS SDK + +A client of Salt Edge Authenticator API v2 of Salt Edge SCA Service. Implements Strong Customer Authentication/Dynamic Linking process. + +## Requirements + +- iOS 10.0+ +- Xcode 10.2+ +- Swift 5+ + +## Installation + +### CocoaPods + +CocoaPods is a dependency manager for Cocoa projects. For usage and installation instructions, visit their website. +To integrate `SaltedgeAuthenticatorSDK` into your Xcode project using CocoaPods, specify it in your Podfile: + +`pod 'SaltedgeAuthenticatorSDK'` + +## How to use + +Authenticator SDK provide next features: +* [Create connection (Link Bank flow)](#create-connection) +* [Remove connection (Remove Bank)](#remove-connection) +* [Fetch authorizations list](#fetch-authorizations-list) +* [Fetch authorization by ID](#fetch-authorization-by-id) +* [Confirm authorization](#confirm-authorization) +* [Deny authorization](#deny-authorization) +* [Send instant action](#send-instant-action) +* [Get User Consents](#Get-User-Consents) +* [Revoke Consent](#Revoke-Consent) + +### Data models + +#### Connection + * `guid` **[string]** - Alias to RSA keypair + * `id` **[string]** - Unique id received from Authenticator API + * `name` **[string]** - Provider's name from `SEProviderResponse` + * `code` **[string]** - Provider's code + * `logoUrl` **[string]** - Provider's logo url. May be empty + * `connectUrl` **[string]** - Base url of Authenticator API + * `accessToken` **[string]** - Access token for accessing Authenticator API resources + * `status` **[string]** - Connection Status. ACTIVE or INACTIVE + +#### SEEncryptedData + * `algorithm` **[string]** - encryption algorithm and block mode type + * `iv` **[string]** - an initialization vector of encryption algorithm, this string is encrypted with public key linked to mobile client + * `key` **[string]** - an secure key of encryption algorithm, this string is encrypted with public key linked to mobile client + * `data` **[string]** - encrypted payload (Authorization/Consent) with algorithm mentioned above + * `connection_id` **[string]** - Optional. A unique ID of Mobile Client (Service Connection). Used to decrypt models in the mobile application + +#### SEAuthorizationDataV2 + * `id` **[string]** - a unique id of authorization action + * `connection_id` **[string]** - a unique ID of Connection. Used to decrypt models in the mobile application + * `title` **[string]** - a human-readable title of authorization action + * `description` **[string]** - a human-readable description of authorization action + * `authorization_code` **[string]** - Optional. A unique code for each operation (e.g. payment transaction), specific to the attributes of operation, must be used once + * ` apiVersion` **[string]** - current version of API + * `created_at` **[datetime]** - time when the authorization was created + * `expires_at` **[datetime]** - time when the authorization should expire + + #### SEConsentData + * `id` **[string]** - a unique id of Consent object + * `connection_id` **[string]** - a unique ID of Connection. Used to decrypt models in the mobile application + * `title` **[string]** - a human-readable title of Consent object + * `status` **[string]** - current Status of Authorization (pending, processing, confirmed, denied, error, timeOut, unavailable, confirm_processing, deny_processing, data) + * `description` **[string]** - a human-readable description of Consent object (plain text, html, json, etc.) + * `created_at` **[datetime]** - time when the authorization was created + * `expires_at` **[datetime]** - time when the authorization should expire + +### Responses + +##### SEProviderResponseV2 + * `name` **[string]** - Provider's name + * `code` **[string]** - Provider's code + * `logoUrl` **[string]** - Optional. Provider's logo url. + * `baseUrl` **[string]** - Base url of SCA Service + * `providerId` **[string]** - a unique identificator of Provider + * `supportEmail` **[string]** - email address of Provider's Customer Support + * `publicKey` **[string]** - asymmetric RSA Public Key (in PEM format) linked to the Provider (registered as Client in SCA Service) + * `geolocationRequired` **[boolean]** - collection of geolocation data is mandatory or not + +##### SECreateConnectionResponse + * `authenticationUrl` **[string]** - URL OAuth Authentication Page for future end-user authentication + * `id` **[string]** - an ID of current connection + +##### SEConfirmAuthorizationResponseV2 + * `id` **[string]** - a unique id of authorization + * `status` **[string]** - current status of authorization model (e.g. pending) + +##### SERevokeConnectionResponseV2 + * `id` **[connection_id]** - a unique identifier of Connection of SCA Service + +### Create connection + +1. Scan QR code + +2. Extract qr code content (deep link) +`authenticator://saltedge.com/connect?configuration=https://example.com/configuration` + +3. Extract configuration url from deep link. + +Use extraction method from `SEConnectHelper.swift` + ```swift + let configurationUrl = SEConnectHelper.configuration(from: deepLink) + ``` + +4. Fetch Provider Data from configuration url + - parameters: + - `url`: the url, which will be use to make request. + + ```swift + SEProviderManagerV2.fetchProviderData( + url: configurationUrl, + onSuccess: { response in + // handle SEProviderResponseV2 + }, + onFailure: { error in + // handle error + } + ) + ``` + +5. Create `SEConnectionData` model, where will be created keypair using `tag`. + - parameters: + - `code`: The code of the provider + - `tag`: The tag, which will be used for creating keypair + + ```swift + let connectionData = SEConnectionData(code: providerCode, tag: connectionGuid) + ``` + +6. Post `SEConnectionData` and receive authorization url (`connect_url`), using `SEConnectionManagerV2.createConnection` method. + - parameters: + - `url`: the url, which will be use to make request. + - `data`: `SEConnectionData` + - `pushToken`: Unique device token, which will be used as device identifier. + - `appLanguage`: Request header to identify preferred language. + + ```swift + SEConnectionManagerV2.createConnection( + by: connectionUrl, + data: connectionData, + pushToken: pushToken, + appLanguage: "en", + success: { response in + // assign received id as connection id + // use received connectUrl string for openning webView for future user authentication + }, + failure: { + // handle error + } + ) + ``` + +7. Pass `connectUrl` to instance of `SEWebView` *(For OAuth authentication)*. + + ```swift + let request = URLRequest(url: connectUrl) + seWebView.load(request) + ``` + +8. After passing user authencation, webView will catch `accessToken` or `error`. Result will be returned through `SEWebViewDelegate` *(For OAuth authentication)*. + + ```swift + func webView(_ webView: WKWebView, didReceiveCallback url: URL, accessToken: AccessToken) { + // save accessToken to Connection model and navigate to next step + } + + func webView(_ webView: WKWebView, didReceiveCallbackWithError error: String?) { + // handle error + } + ``` + +9. Set `accessToken` to `Connection` and save `Connection` to persistent storage (e.g. Realm, CoreData). + +That's all, now you have connection to the Bank (Service Provider). + +### Remove Connection + +1. Send revoke request + - parameters: + - `url`: the url, which will be use to make request. + - `data`: `SERevokeConnectionData` + - `appLanguage`: Request header to identify preferred language. + + ```swift + let data = SEBaseAuthenticatedWithIdRequestData( + url: baseUrl, + connectionGuid: connection.guid, + accessToken: connection.accessToken, + appLanguage: UserDefaultsHelper.applicationLanguage, + entityId: connection.id // The id of requested entity (authorization, connection, consent) + ) + + SEConnectionManagerV2.revokeConnection( + data: data, + onSuccess: { response in + // handle success callback here + }, + onFailure: { error in + // handle error + } + ) + ``` + +2. Delete connections from persistent storage + +3. Delete related key pairs from keychain + ```swift + SECryptoHelper.deleteKeyPair(with: SETagHelper.create(for: connection.guid)) + ``` +### Fetch authorizations list + +1. For periodically fetching of authorizations list, implement polling service. You may use Swift Timer which will request pending Authorizations every 3 seconds. + +```swift + var pollingTimer: Timer? + + func startPolling() { + getEncryptedAuthorizations() + + pollingTimer = Timer.scheduledTimer( + timeInterval: 3.0, + target: self, + selector: #selector(getEncryptedAuthorizations), + userInfo: nil, + repeats: true + ) + } +``` + +To stop polling, just invalidate timer and set it to nil: + +```swift + func stopPolling() { + pollingTimer?.invalidate() + pollingTimer = nil + } +``` + +2. Send request + - parameters: + - `SEBaseAuthenticatedRequestData`: + - `url`: the url, which will be use to make request. + - `connectionGuid`: the uniq guid of the connection. + - `accessToken`: a unique token string for authenticated access to API resources. + - `appLanguage`: request header to identify preferred language. + + ```swift + SEAuthorizationManager.getEncryptedAuthorizations( + data: SEBaseAuthenticatedRequestData( + url: baseUrl, + connectionGuid: connection.guid, + accessToken: accessToken, + appLanguage: UserDefaultsHelper.applicationLanguage + ), + onSuccess: { response in + // handle encrypted authorizations response + }, + onFailure: { error in + // handle error + } + ) + ``` + +3. Decrypt authorization response, using `SECryptoHelper.decrypt` method. + - parameters: + - `SEEncryptedData`: + - `iv`: an initialization vector of encryption algorithm, this string is encrypted with public key linked to mobile client. + - `key`: an secure key of encryption algorithm, this string is encrypted with public key linked to mobile client. + - `data`: encrypted authorization payload with algorithm. + - `algorithm`: encryption algorithm and block mode type. + - `connectionId`: unique ID of connection + + ```swift + let decryptedData = try SECryptoHelper.decrypt(encryptedData, tag: SETagHelper.create(for: connection.guid)) + + guard let decryptedDictionary = decryptedData.json else { return nil } + + return SEAuthorizationDataV2(decryptedDictionary) + ``` + +3. Show decrypted Authorizations to user + +### Fetch authorization by ID + +1. Send request + - parameters: + - `SEBaseAuthenticatedWithIdRequestData`: + - `entityId`: the id of authorization + - `url`: the url, which will be use to make request. + - `connectionGuid`: the uniq guid of the connection. + - `accessToken`: a unique token string for authenticated access to API resources. + - `appLanguage`: request header to identify preferred language. + + ```swift + SEAuthorizationManagerV2.getEncryptedAuthorization( + data: authorizationData, + onSuccess: { response in + // handle SEEncryptedAuthorizationResponse + }, + onFailure: { error in + // handle error + } + ) + ``` + +2. Decrypt authorization response, using `SECryptoHelper.decrypt` method. + - parameters: + - `SEEncryptedData`: + - `iv`: an initialization vector of encryption algorithm, this string is encrypted with public key linked to mobile client. + - `key`: an secure key of encryption algorithm, this string is encrypted with public key linked to mobile client. + - `data`: encrypted authorization payload with algorithm. + - `algorithm`: encryption algorithm and block mode type. + - `connectionId`: unique ID of connection + + ```swift + let decryptedData = try SECryptoHelper.decrypt(encryptedData, tag: SETagHelper.create(for: connection.guid)) + + guard let decryptedDictionary = decryptedData.json else { return nil } + + return SEAuthorizationData(decryptedDictionary) + ``` + +3. Show decrypted Authorization to user + +### Confirm authorization + +User can confirm authorization +- parameters: + - `SEConfirmAuthorizationRequestData`: + - `authorizationId`: the uniq id of authorization to confirm + - `url`: the url, which will be use to make request. + - `connectionGuid`: the uniq guid of the connection. + - `accessToken`: a unique token string for authenticated access to API resources. + - `appLanguage`: request header to identify preferred language. + - `authorizationCode`: Optional. Generated unique code per each authorization action based on set of input information (datetime, amount, payee, account, etc.) + +```swift + SEAuthorizationManagerV2.confirmAuthorization( + data: confirmAuthData, + onSuccess: { response in + // handle success here + }, + onFailure: { error in + // handle error + } + ) +``` + +### Deny authorization + +User can deny authorization +- parameters: + - `SEConfirmAuthorizationRequestData`: + - `authorizationId`: the uniq id of authorization to confirm + - `url`: the url, which will be used to make request. + - `connectionGuid`: the uniq guid of the connection. + - `accessToken`: a unique token string for authenticated access to API resources. + - `appLanguage`: Request header to identify preferred language. + - `authorizationCode`: Optional. + +```swift + SEAuthorizationManager.denyAuthorization( + data: denyAuthData, + onSuccess: { response in + // handle success here + }, + onFailure: { error in + // handle error + } + ) +``` + +### Send Instant Action + +Instant Action feature is designated to authenticate an action of Service Provider (e.g. Sign-In, Payment Order). Each Instant Action has unique code `actionGuid`. After receiving of `actionGuid`, Authenticator app should submit to selected by user Connection: + +- parameters: + - `SEActionRequestDataV2`: + - `url`: the url, which will be used to make request. + - `actionId`: the unique identifier of the action. + - `providerId`: the unique identifier of the provider. + - `connectionId`: the unique identifier of the Connection where action will be submitted. + - `accessToken`: a unique token string for authenticated access to API resources. + - `appLanguage`: Request header to identify preferred language. + +```swift + let actionData = SEActionRequestDataV2( + url: connectUrl, + connectionId: connection.id, + accessToken: connection.accessToken, + appLanguage: UserDefaultsHelper.applicationLanguage, + providerId: providerId, + actionId: actionId, + ) + + SEActionManager.submitAction( + data: actionData, + onSuccess: { response in + // handle success here + }, + onFailure: { error in + // handle error + } + ) +``` + +On success, Authenticator app receives `SESubmitActionResponseV2` which has optional fields `connectionId` and `authorizationId` (if additional confirmation is required). + +### Get User Consents + +1. Send request + - parameters: + - `SEBaseAuthenticatedRequestData`: + - `url`: the url, which will be use to make request. + - `connectionGuid`: the uniq guid of the connection. + - `accessToken`: a unique token string for authenticated access to API resources. + - `appLanguage`: request header to identify preferred language. + + ```swift + SEAuthorizationManager.getEncryptedConsents( + data: SEBaseAuthenticatedRequestData( + url: baseUrl, + connectionGuid: connection.guid, + accessToken: accessToken, + appLanguage: UserDefaultsHelper.applicationLanguage + ), + onSuccess: { response in + // handle encrypted consents response + }, + onFailure: { error in + // handle error + } + ) + ``` + +2. Decrypt authorization response, using `SECryptoHelper.decrypt` method. + - parameters: + - `SEEncryptedData`: + - `iv`: an initialization vector of encryption algorithm, this string is encrypted with public key linked to mobile client. + - `key`: an secure key of encryption algorithm, this string is encrypted with public key linked to mobile client. + - `data`: encrypted Consent object. + - `algorithm`: encryption algorithm and block mode type. + - `connectionId`: unique ID of connection + + ```swift + let decryptedData = try SECryptoHelper.decrypt(encryptedData, tag: SETagHelper.create(for: connection.guid)) + + guard let decryptedDictionary = decryptedData.json else { return nil } + + return SEConsentData(decryptedDictionary) + ``` + +3. Show decrypted Consents to user + +### Revoke Consent + +1. Send revoke request + - parameters: + - `SEBaseAuthenticatedWithIdRequestData`: + - `entityId`: the id of authorization + - `url`: the url, which will be use to make request. + - `connectionGuid`: the uniq guid of the connection. + - `accessToken`: a unique token string for authenticated access to API resources. + - `appLanguage`: request header to identify preferred language. + + ```swift + SEAuthorizationManagerV2.revokeConsent( + data: authorizationData, + onSuccess: { response in + // handle success result + }, + onFailure: { error in + // handle error + } + ) + ``` + +--- +Copyright © 2022 Salt Edge. https://www.saltedge.com diff --git a/SaltedgeAuthenticatorSDKv2/SaltedgeAuthenticatorSDKv2.podspec b/SaltedgeAuthenticatorSDKv2/SaltedgeAuthenticatorSDKv2.podspec index dc75e5c8..8c12eb1d 100644 --- a/SaltedgeAuthenticatorSDKv2/SaltedgeAuthenticatorSDKv2.podspec +++ b/SaltedgeAuthenticatorSDKv2/SaltedgeAuthenticatorSDKv2.podspec @@ -28,6 +28,5 @@ Pod::Spec.new do |s| s.source_files = '**/Classes/**/*' s.dependency 'SaltedgeAuthenticatorCore' -# s.dependency 'CryptoSwift' s.dependency 'JOSESwift' end From 1d78bc3545771d026cf0b803f53a73d828329782 Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Fri, 25 Nov 2022 15:21:40 +0200 Subject: [PATCH 108/110] updated v2 readme --- .../Utils/Handlers/ConnectHandler.swift | 2 +- .../API/Managers/SEConnectionManagerV2.swift | 8 +- ...ift => SECreateConnectionResponseV2.swift} | 4 +- ...ift => SERevokeConnectionResponseV2.swift} | 4 +- SaltedgeAuthenticatorSDKv2/README.md | 100 ++++++++++++------ 5 files changed, 76 insertions(+), 42 deletions(-) rename SaltedgeAuthenticatorSDKv2/Classes/API/Responses/{SECreateConnectionResponse.swift => SECreateConnectionResponseV2.swift} (94%) rename SaltedgeAuthenticatorSDKv2/Classes/API/Responses/{SERevokeConnectionResponse.swift => SERevokeConnectionResponseV2.swift} (94%) diff --git a/Example/Authenticator/Utils/Handlers/ConnectHandler.swift b/Example/Authenticator/Utils/Handlers/ConnectHandler.swift index 218ddd90..c08ace7f 100644 --- a/Example/Authenticator/Utils/Handlers/ConnectHandler.swift +++ b/Example/Authenticator/Utils/Handlers/ConnectHandler.swift @@ -111,7 +111,7 @@ private extension ConnectHandler { self?.connection = connection self?.saveConnectionAndFinish(with: accessToken) }, - redirect: { [weak self] connection, connectUrl in + redirect: { [weak self] connection, connectUrl in // url designated for user authorization self?.connection = connection self?.delegate?.startWebViewLoading(with: connectUrl) }, diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConnectionManagerV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConnectionManagerV2.swift index c9ef47ec..357a85ee 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConnectionManagerV2.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEConnectionManagerV2.swift @@ -28,10 +28,10 @@ public struct SEConnectionManagerV2 { by url: URL, params: SECreateConnectionParams, appLanguage: ApplicationLanguage, - onSuccess success: SEHTTPResponse, + onSuccess success: SEHTTPResponse, onFailure failure: @escaping FailureBlock ) { - HTTPService.makeRequest( + HTTPService.makeRequest( SEConnectionRouter.createConnection(url, params, appLanguage), completion: success, failure: failure @@ -40,10 +40,10 @@ public struct SEConnectionManagerV2 { public static func revokeConnection( data: SEBaseAuthenticatedWithIdRequestData, - onSuccess success: SEHTTPResponse, + onSuccess success: SEHTTPResponse, onFailure failure: @escaping FailureBlock ) { - HTTPService.makeRequest( + HTTPService.makeRequest( SEConnectionRouter.revoke(data), completion: success, failure: failure diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SECreateConnectionResponse.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SECreateConnectionResponseV2.swift similarity index 94% rename from SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SECreateConnectionResponse.swift rename to SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SECreateConnectionResponseV2.swift index 98b223c1..9ad2be01 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SECreateConnectionResponse.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SECreateConnectionResponseV2.swift @@ -1,5 +1,5 @@ // -// SECreateConnectionResponse +// SECreateConnectionResponseV2 // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) // Copyright © 2021 Salt Edge Inc. @@ -23,7 +23,7 @@ import Foundation import SEAuthenticatorCore -public struct SECreateConnectionResponse: Decodable { +public struct SECreateConnectionResponseV2: Decodable { public let id: String public let authenticationUrl: String diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConnectionResponse.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConnectionResponseV2.swift similarity index 94% rename from SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConnectionResponse.swift rename to SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConnectionResponseV2.swift index cefe761b..8bb37c95 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConnectionResponse.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Responses/SERevokeConnectionResponseV2.swift @@ -1,5 +1,5 @@ // -// SERevokeConnectionResponse +// SERevokeConnectionResponseV2 // This file is part of the Salt Edge Authenticator distribution // (https://github.com/saltedge/sca-authenticator-ios) // Copyright © 2021 Salt Edge Inc. @@ -23,7 +23,7 @@ import Foundation import SEAuthenticatorCore -public struct SERevokeConnectionResponse: Decodable { +public struct SERevokeConnectionResponseV2: Decodable { public let connectionId: String enum CodingKeys: String, CodingKey { diff --git a/SaltedgeAuthenticatorSDKv2/README.md b/SaltedgeAuthenticatorSDKv2/README.md index 8b7474c4..dda05e32 100644 --- a/SaltedgeAuthenticatorSDKv2/README.md +++ b/SaltedgeAuthenticatorSDKv2/README.md @@ -80,7 +80,7 @@ Authenticator SDK provide next features: * `publicKey` **[string]** - asymmetric RSA Public Key (in PEM format) linked to the Provider (registered as Client in SCA Service) * `geolocationRequired` **[boolean]** - collection of geolocation data is mandatory or not -##### SECreateConnectionResponse +##### SECreateConnectionResponseV2 * `authenticationUrl` **[string]** - URL OAuth Authentication Page for future end-user authentication * `id` **[string]** - an ID of current connection @@ -121,16 +121,53 @@ Use extraction method from `SEConnectHelper.swift` ) ``` -5. Create `SEConnectionData` model, where will be created keypair using `tag`. +5. Create Provider's public key (SecKey) + + ```swift + SECryptoHelper.createKey( + from: connection.publicKey, + isPublic: true, + tag: connection.providerPublicKeyTag + ) + ``` + +6. Generate new RSA key pair for a new Connection by tag + + ```swift + SECryptoHelper.createKeyPair(with: SETagHelper.create(for: connection.guid)) + ``` + +7. Convert Connection's public key to pem + + ```swift + let connectionPublicKeyPem = SECryptoHelper.publicKeyToPem(tag: SETagHelper.create(for: connection.guid)), + ``` +8. Encrypt Connection's public key with Provider's public key (step 5) + + ```swift + let encryptedData = try? SECryptoHelper.encrypt( + connectionPublicKeyPem, + tag: connection.providerPublicKeyTag + ) + ``` + +9. Create `SECreateConnectionParams` model, where will be created keypair using `tag`. - parameters: - - `code`: The code of the provider - - `tag`: The tag, which will be used for creating keypair + - `providerId`: The id of Connection's Provider + - `pushToken`: The push token of the devise + - `connectQuery`: Token which uniquely identifies the user which requires creation of new connection. + - `encryptedRsaPublicKey`: Connection's public key with Provider's public key (step 8) ```swift - let connectionData = SEConnectionData(code: providerCode, tag: connectionGuid) + let connectionParams = SECreateConnectionParams( + providerId: providerId, + pushToken: pushToken, + connectQuery: connectQuery, + encryptedRsaPublicKey: encryptedRsaPublicKey + ) ``` -6. Post `SEConnectionData` and receive authorization url (`connect_url`), using `SEConnectionManagerV2.createConnection` method. +10. Post `SECreateConnectionParams` and receive authorization url (`connect_url`), using `SEConnectionManagerV2.createConnection` method. - parameters: - `url`: the url, which will be use to make request. - `data`: `SEConnectionData` @@ -139,13 +176,13 @@ Use extraction method from `SEConnectHelper.swift` ```swift SEConnectionManagerV2.createConnection( - by: connectionUrl, - data: connectionData, + by: connectionBaseUrl, // Base url of the Connection + params: connectionData, pushToken: pushToken, appLanguage: "en", success: { response in // assign received id as connection id - // use received connectUrl string for openning webView for future user authentication + // use received connectUrl string for openning a webView for future user authentication }, failure: { // handle error @@ -153,14 +190,14 @@ Use extraction method from `SEConnectHelper.swift` ) ``` -7. Pass `connectUrl` to instance of `SEWebView` *(For OAuth authentication)*. +11. Pass `connectUrl` to instance of `SEWebView` *(For OAuth authentication)*. ```swift let request = URLRequest(url: connectUrl) seWebView.load(request) ``` -8. After passing user authencation, webView will catch `accessToken` or `error`. Result will be returned through `SEWebViewDelegate` *(For OAuth authentication)*. +12. After passing user authencation, webView will catch `accessToken` or `error`. Result will be returned through `SEWebViewDelegate` *(For OAuth authentication)*. ```swift func webView(_ webView: WKWebView, didReceiveCallback url: URL, accessToken: AccessToken) { @@ -172,7 +209,7 @@ Use extraction method from `SEConnectHelper.swift` } ``` -9. Set `accessToken` to `Connection` and save `Connection` to persistent storage (e.g. Realm, CoreData). +13. Set `accessToken` to `Connection` and save `Connection` to persistent storage (e.g. Realm, CoreData). That's all, now you have connection to the Bank (Service Provider). @@ -180,9 +217,12 @@ That's all, now you have connection to the Bank (Service Provider). 1. Send revoke request - parameters: - - `url`: the url, which will be use to make request. - - `data`: `SERevokeConnectionData` - - `appLanguage`: Request header to identify preferred language. + - `SEBaseAuthenticatedWithIdRequestData`: + - `entityId`: the id of authorization + - `url`: the url, which will be use to make request. + - `connectionGuid`: the uniq guid of the connection. + - `accessToken`: a unique token string for authenticated access to API resources. + - `appLanguage`: request header to identify preferred language. ```swift let data = SEBaseAuthenticatedWithIdRequestData( @@ -212,21 +252,15 @@ That's all, now you have connection to the Bank (Service Provider). ``` ### Fetch authorizations list -1. For periodically fetching of authorizations list, implement polling service. You may use Swift Timer which will request pending Authorizations every 3 seconds. +1. For periodically fetching of authorizations list, implement a polling service. You may use `SEPoller` which will request pending Authorizations every 3 seconds. ```swift - var pollingTimer: Timer? + var pollingTimer: SEPoller? - func startPolling() { + func startPolling() { + poller = SEPoller(targetClass: self, selector: #selector(getEncryptedAuthorizations)) getEncryptedAuthorizations() - - pollingTimer = Timer.scheduledTimer( - timeInterval: 3.0, - target: self, - selector: #selector(getEncryptedAuthorizations), - userInfo: nil, - repeats: true - ) + poller?.startPolling() } ``` @@ -234,8 +268,8 @@ To stop polling, just invalidate timer and set it to nil: ```swift func stopPolling() { - pollingTimer?.invalidate() - pollingTimer = nil + poller?.stopPolling() + poller = nil } ``` @@ -248,7 +282,7 @@ To stop polling, just invalidate timer and set it to nil: - `appLanguage`: request header to identify preferred language. ```swift - SEAuthorizationManager.getEncryptedAuthorizations( + SEAuthorizationManagerV2.getEncryptedAuthorizations( data: SEBaseAuthenticatedRequestData( url: baseUrl, connectionGuid: connection.guid, @@ -354,15 +388,15 @@ User can confirm authorization User can deny authorization - parameters: - `SEConfirmAuthorizationRequestData`: - - `authorizationId`: the uniq id of authorization to confirm - `url`: the url, which will be used to make request. - `connectionGuid`: the uniq guid of the connection. - `accessToken`: a unique token string for authenticated access to API resources. - `appLanguage`: Request header to identify preferred language. + - `authorizationId`: the uniq id of authorization to confirm - `authorizationCode`: Optional. ```swift - SEAuthorizationManager.denyAuthorization( + SEAuthorizationManagerV2.denyAuthorization( data: denyAuthData, onSuccess: { response in // handle success here @@ -396,7 +430,7 @@ Instant Action feature is designated to authenticate an action of Service Provid actionId: actionId, ) - SEActionManager.submitAction( + SEActionManagerV2.submitAction( data: actionData, onSuccess: { response in // handle success here @@ -420,7 +454,7 @@ On success, Authenticator app receives `SESubmitActionResponseV2` which has opti - `appLanguage`: request header to identify preferred language. ```swift - SEAuthorizationManager.getEncryptedConsents( + SEConsentManagerV2.getEncryptedConsents( data: SEBaseAuthenticatedRequestData( url: baseUrl, connectionGuid: connection.guid, From 4b3c128fa6de20039fc36e3a9e54837a8587798f Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Fri, 25 Nov 2022 16:00:05 +0200 Subject: [PATCH 109/110] removed gelocation comments and blocking note --- .../Coordinators/ConnectViewCoordinator.swift | 27 +++++++++---------- .../Settings/PasscodeCoordinator.swift | 3 +-- .../AuthorizationsDataSource.swift | 7 +++-- .../Utils/Handlers/ConnectHandler.swift | 11 ++++---- .../v1/ConnectionsInteractor.swift | 4 +-- .../SingleAuthorizationViewModel.swift | 14 +++++----- .../AuthorizationContentView.swift | 27 ++++++++----------- .../Routers/v1/AuthorizationRouterSpec.swift | 24 +++++++---------- README.md | 1 + .../Models/SEConfirmAuthorizationData.swift | 18 +++++-------- .../API/Routers/SEAuthorizationRouter.swift | 5 ++-- .../Managers/SEAuthorizationManagerV2.swift | 7 +++-- SaltedgeAuthenticatorSDKv2/README.md | 6 ++--- 13 files changed, 66 insertions(+), 88 deletions(-) diff --git a/Example/Authenticator/Coordinators/ConnectViewCoordinator.swift b/Example/Authenticator/Coordinators/ConnectViewCoordinator.swift index e3a9eb72..5ee044a7 100644 --- a/Example/Authenticator/Coordinators/ConnectViewCoordinator.swift +++ b/Example/Authenticator/Coordinators/ConnectViewCoordinator.swift @@ -89,20 +89,19 @@ extension ConnectViewCoordinator: ConnectEventsDelegate { connectViewController.showCompleteView(with: .success, title: "", attributedTitle: attributedMessage) } - // NOTE: Temporarily inactive due to legal restrictions - // func requestLocationAuthorization() { - // if LocationManager.shared.notDeterminedAuthorization { - // LocationManager.shared.requestLocationAuthorization() - // } else { - // connectViewController.showInfoAlert( - // withTitle: l10n(.turnOnLocationServices), - // message: l10n(.turnOnPhoneLocationServicesDescription), - // completion: { - // LocationManager.shared.requestLocationAuthorization() - // } - // ) - // } - // } + func requestLocationAuthorization() { + if LocationManager.shared.notDeterminedAuthorization { + LocationManager.shared.requestLocationAuthorization() + } else { + connectViewController.showInfoAlert( + withTitle: l10n(.turnOnLocationServices), + message: l10n(.turnOnPhoneLocationServicesDescription), + completion: { + LocationManager.shared.requestLocationAuthorization() + } + ) + } + } func startWebViewLoading(with connectUrlString: String) { webViewController.startLoading(with: connectUrlString) diff --git a/Example/Authenticator/Coordinators/Settings/PasscodeCoordinator.swift b/Example/Authenticator/Coordinators/Settings/PasscodeCoordinator.swift index e6dc23a3..71079534 100644 --- a/Example/Authenticator/Coordinators/Settings/PasscodeCoordinator.swift +++ b/Example/Authenticator/Coordinators/Settings/PasscodeCoordinator.swift @@ -49,8 +49,7 @@ final class PasscodeCoordinator: Coordinator { viewModel.delegate = self currentViewController.onCompleteClosure = { (_ completeType: CompleteType) -> () in - // NOTE: Temporarily inactive due to legal restrictions -// PasscodeCoordinator.lastAppUnlockCompleteType = completeType + PasscodeCoordinator.lastAppUnlockCompleteType = completeType self.onCompleteClosure?() } diff --git a/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift b/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift index 1152cd0d..c1e7c869 100644 --- a/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift +++ b/Example/Authenticator/Models/Data Sources/AuthorizationsDataSource.swift @@ -100,10 +100,9 @@ final class AuthorizationsDataSource { accessToken: connection.accessToken, appLanguage: UserDefaultsHelper.applicationLanguage, authorizationId: viewModel.authorizationId, - authorizationCode: viewModel.authorizationCode - // NOTE: Temporarily inactive due to legal restrictions - // geolocation: LocationManager.currentLocation?.headerValue, - // authorizationType: PasscodeCoordinator.lastAppUnlockCompleteType.rawValue + authorizationCode: viewModel.authorizationCode, + geolocation: LocationManager.currentLocation?.headerValue, + authorizationType: PasscodeCoordinator.lastAppUnlockCompleteType.rawValue ) } diff --git a/Example/Authenticator/Utils/Handlers/ConnectHandler.swift b/Example/Authenticator/Utils/Handlers/ConnectHandler.swift index c08ace7f..85f0c5b4 100644 --- a/Example/Authenticator/Utils/Handlers/ConnectHandler.swift +++ b/Example/Authenticator/Utils/Handlers/ConnectHandler.swift @@ -23,13 +23,13 @@ import Foundation import SEAuthenticatorCore -protocol ConnectEventsDelegate: class { +protocol ConnectEventsDelegate: AnyObject { func showWebViewController() func finishConnectWithSuccess(attributedMessage: NSMutableAttributedString) func startWebViewLoading(with connectUrlString: String) func dismiss() func dismissConnectWithError(error: String) -// func requestLocationAuthorization() // Note: Temporarily inactive due to legal restrictions + func requestLocationAuthorization() } final class ConnectHandler { @@ -75,10 +75,9 @@ final class ConnectHandler { finalString.append(description) delegate?.finishConnectWithSuccess(attributedMessage: finalString) - // Note: Temporarily inactive dut to legal restrictions -// if connection.geolocationRequired.value != nil && LocationManager.shared.notDeterminedAuthorization { -// delegate?.requestLocationAuthorization() -// } + if connection.geolocationRequired.value != nil { + delegate?.requestLocationAuthorization() + } } } diff --git a/Example/Authenticator/Utils/Interactors/v1/ConnectionsInteractor.swift b/Example/Authenticator/Utils/Interactors/v1/ConnectionsInteractor.swift index 6c06c364..3b6daea8 100644 --- a/Example/Authenticator/Utils/Interactors/v1/ConnectionsInteractor.swift +++ b/Example/Authenticator/Utils/Interactors/v1/ConnectionsInteractor.swift @@ -47,9 +47,7 @@ struct ConnectionsInteractor: BaseConnectionsInteractor { connection.supportEmail = response.supportEmail connection.logoUrlString = response.logoUrl?.absoluteString ?? "" connection.baseUrlString = response.baseUrl.absoluteString - - // Note: Temporarily inactive due to legal restrictions - // connection.geolocationRequired.value = response.geolocationRequired + connection.geolocationRequired.value = response.geolocationRequired submitNewConnection( for: connection, diff --git a/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift b/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift index fcab70cd..042810a1 100644 --- a/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift +++ b/Example/Authenticator/View Models/Authorizations/SingleAuthorizationViewModel.swift @@ -129,10 +129,9 @@ extension SingleAuthorizationViewModel: AuthorizationDetailEventsDelegate { accessToken: connection.accessToken, appLanguage: UserDefaultsHelper.applicationLanguage, authorizationId: authorizationId, - authorizationCode: detailViewModel.authorizationCode - // NOTE: Temporarily inactive due to legal restrictions - // geolocation: LocationManager.currentLocation?.headerValue, - // authorizationType: PasscodeCoordinator.lastAppUnlockCompleteType.rawValue + authorizationCode: detailViewModel.authorizationCode, + geolocation: LocationManager.currentLocation?.headerValue, + authorizationType: PasscodeCoordinator.lastAppUnlockCompleteType.rawValue ) detailViewModel.state.value = .processing @@ -165,10 +164,9 @@ extension SingleAuthorizationViewModel: AuthorizationDetailEventsDelegate { accessToken: connection.accessToken, appLanguage: UserDefaultsHelper.applicationLanguage, authorizationId: authorizationId, - authorizationCode: detailViewModel.authorizationCode - // NOTE: Temporarily inactive due to legal restrictions - // geolocation: LocationManager.currentLocation?.headerValue, - // authorizationType: PasscodeCoordinator.lastAppUnlockCompleteType.rawValue + authorizationCode: detailViewModel.authorizationCode, + geolocation: LocationManager.currentLocation?.headerValue, + authorizationType: PasscodeCoordinator.lastAppUnlockCompleteType.rawValue ) detailViewModel.state.value = .processing diff --git a/Example/Authenticator/Views/Authorizations/AuthorizationContentView.swift b/Example/Authenticator/Views/Authorizations/AuthorizationContentView.swift index de22655d..785c2ace 100644 --- a/Example/Authenticator/Views/Authorizations/AuthorizationContentView.swift +++ b/Example/Authenticator/Views/Authorizations/AuthorizationContentView.swift @@ -60,19 +60,17 @@ final class AuthorizationContentView: UIView { stackView.spacing = 11.0 return stackView }() - // NOTE: Temporarily inactive due to legal restrictions - // private let locationWarningLabel = UILabel(font: .systemFont(ofSize: 18.0, weight: .regular), textColor: .redAlert) + private let locationWarningLabel = UILabel(font: .systemFont(ofSize: 18.0, weight: .regular), textColor: .redAlert) var viewModel: AuthorizationDetailViewModel! { didSet { titleLabel.text = viewModel.title - // NOTE: Temporarily inactive due to legal restrictions - // if viewModel.showLocationWarning { - // locationWarningLabel.text = l10n(.locationWarning) - // } - // buttonsStackView.isHidden = viewModel.showLocationWarning - // locationWarningLabel.isHidden = !viewModel.showLocationWarning + if viewModel.shouldShowLocationWarning { + locationWarningLabel.text = l10n(.locationWarning) + } + buttonsStackView.isHidden = viewModel.shouldShowLocationWarning + locationWarningLabel.isHidden = !viewModel.shouldShowLocationWarning guard viewModel.state.value == .base else { stateView.set(state: viewModel.state.value) @@ -179,9 +177,7 @@ private extension AuthorizationContentView { // MARK: - Layout extension AuthorizationContentView: Layoutable { func layout() { - addSubviews(titleLabel, contentStackView, buttonsStackView, stateView) // locationWarningLabel - - addSubviews(titleLabel, contentStackView, buttonsStackView, stateView) + addSubviews(titleLabel, contentStackView, buttonsStackView, stateView, locationWarningLabel) titleLabel.top(to: self, offset: Layout.titleLabelTopOffset) titleLabel.centerX(to: self) @@ -200,11 +196,10 @@ extension AuthorizationContentView: Layoutable { buttonsStackView.bottom(to: self, safeAreaLayoutGuide.bottomAnchor, offset: -Layout.bottomOffset) buttonsStackView.centerXToSuperview() - // NOTE: Temporarily inactive due to legal restrictions - // locationWarningLabel.leftToSuperview(offset: Layout.sideOffset) - // locationWarningLabel.rightToSuperview(offset: -Layout.sideOffset) - // locationWarningLabel.bottom(to: self, safeAreaLayoutGuide.bottomAnchor, offset: -Layout.bottomOffset) - // locationWarningLabel.centerXToSuperview() + locationWarningLabel.leftToSuperview(offset: Layout.sideOffset) + locationWarningLabel.rightToSuperview(offset: -Layout.sideOffset) + locationWarningLabel.bottom(to: self, safeAreaLayoutGuide.bottomAnchor, offset: -Layout.bottomOffset) + locationWarningLabel.centerXToSuperview() stateView.edgesToSuperview() } diff --git a/Example/Tests/Networking/Routers/v1/AuthorizationRouterSpec.swift b/Example/Tests/Networking/Routers/v1/AuthorizationRouterSpec.swift index 1f1750f2..a440f70a 100644 --- a/Example/Tests/Networking/Routers/v1/AuthorizationRouterSpec.swift +++ b/Example/Tests/Networking/Routers/v1/AuthorizationRouterSpec.swift @@ -115,10 +115,9 @@ class AuthorizationRouterSpec: BaseSpec { accessToken: "accessToken", appLanguage: "en", authorizationId: "1", - authorizationCode: "code" - // NOTE: Temporarily inactive due to legal restrictions - // geolocation: "GEO:52.506931;13.144558", - // authorizationType: "biometrics" + authorizationCode: "code", + geolocation: "GEO:52.506931;13.144558", + authorizationType: "biometrics" ) let params = RequestParametersBuilder.confirmAuthorization(true, authorizationCode: data.authorizationCode) @@ -137,9 +136,8 @@ class AuthorizationRouterSpec: BaseSpec { signature: signature, appLanguage: "en" ) - // NOTE: Temporarily inactive due to legal restrictions - // .addLocationHeader(geolocation: "GEO:52.506931;13.144558") - // .addAuthorizationTypeHeader(authorizationType: "biometrics") + .addLocationHeader(geolocation: "GEO:52.506931;13.144558") + .addAuthorizationTypeHeader(authorizationType: "biometrics") let expectedRequest = URLRequestBuilder.buildUrlRequest( with: baseUrl.appendingPathComponent("\(baseUrlPath)/\(data.entityId)"), @@ -163,10 +161,9 @@ class AuthorizationRouterSpec: BaseSpec { accessToken: "accessToken", appLanguage: "en", authorizationId: "1", - authorizationCode: "code" - // NOTE: Temporarily inactive due to legal restrictions - // geolocation: "GEO:52.506931;13.144558", - // authorizationType: "biometrics" + authorizationCode: "code", + geolocation: "GEO:52.506931;13.144558", + authorizationType: "biometrics" ) let params = RequestParametersBuilder.confirmAuthorization(false, authorizationCode: data.authorizationCode) @@ -185,9 +182,8 @@ class AuthorizationRouterSpec: BaseSpec { signature: signature, appLanguage: "en" ) - // NOTE: Temporarily inactive due to legal restrictions - // .addLocationHeader(geolocation: "GEO:52.506931;13.144558") - // .addAuthorizationTypeHeader(authorizationType: "biometrics") + .addLocationHeader(geolocation: "GEO:52.506931;13.144558") + .addAuthorizationTypeHeader(authorizationType: "biometrics") let expectedRequest = URLRequestBuilder.buildUrlRequest( with: baseUrl.appendingPathComponent("\(baseUrlPath)/\(data.entityId)"), diff --git a/README.md b/README.md index 009f57bd..495916f0 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ You can find related source code: ## How it works * ### [Authenticator iOS SDK](SaltedgeAuthenticatorSDK/README.md) +* ### [Authenticator iOS SDK v2](SaltedgeAuthenticatorSDKv2/README.md) * ### [Authenticator iOS app workflow](docs/WORKFLOW.md) * ### [Authenticator Identity Service Wiki](https://github.com/saltedge/sca-identity-service-example/wiki) * ### [Authenticator Identity Service API](https://github.com/saltedge/sca-identity-service-example/blob/master/docs/IDENTITY_SERVICE_API.md) diff --git a/SaltedgeAuthenticatorCore/Classes/API/Models/SEConfirmAuthorizationData.swift b/SaltedgeAuthenticatorCore/Classes/API/Models/SEConfirmAuthorizationData.swift index f40cae44..aa93871e 100644 --- a/SaltedgeAuthenticatorCore/Classes/API/Models/SEConfirmAuthorizationData.swift +++ b/SaltedgeAuthenticatorCore/Classes/API/Models/SEConfirmAuthorizationData.swift @@ -24,10 +24,8 @@ import Foundation public class SEConfirmAuthorizationRequestData: SEBaseAuthenticatedWithIdRequestData { public let authorizationCode: String? - - // NOTE: Temporarily inactive due to legal restrictions - // public let geolocation: String? - // public let authorizationType: String + public let geolocation: String? + public let authorizationType: String public init( url: URL, @@ -35,15 +33,13 @@ public class SEConfirmAuthorizationRequestData: SEBaseAuthenticatedWithIdRequest accessToken: AccessToken, appLanguage: ApplicationLanguage, authorizationId: ID, - authorizationCode: String? - // NOTE: Temporarily inactive due to legal restrictions - // geolocation: String?, - // authorizationType: String + authorizationCode: String?, + geolocation: String?, + authorizationType: String ) { self.authorizationCode = authorizationCode - // NOTE: Temporarily inactive due to legal restrictions - // self.geolocation = geolocation - // self.authorizationType = authorizationType + self.geolocation = geolocation + self.authorizationType = authorizationType super.init( url: url, connectionGuid: connectionGuid, diff --git a/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEAuthorizationRouter.swift b/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEAuthorizationRouter.swift index 5e7e5f8e..f4da208c 100644 --- a/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEAuthorizationRouter.swift +++ b/SaltedgeAuthenticatorSDK/Classes/API/Routers/SEAuthorizationRouter.swift @@ -111,9 +111,8 @@ enum SEAuthorizationRouter: Routable { signature: signature, appLanguage: data.appLanguage ) - // NOTE: Temporarily inactive due to legal restrictions -// .addLocationHeader(geolocation: data.geolocation) -// .addAuthorizationTypeHeader(authorizationType: data.authorizationType) + .addLocationHeader(geolocation: data.geolocation) + .addAuthorizationTypeHeader(authorizationType: data.authorizationType) } } diff --git a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEAuthorizationManagerV2.swift b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEAuthorizationManagerV2.swift index 49fe8fbc..64797183 100644 --- a/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEAuthorizationManagerV2.swift +++ b/SaltedgeAuthenticatorSDKv2/Classes/API/Managers/SEAuthorizationManagerV2.swift @@ -85,10 +85,9 @@ public struct SEAuthorizationManagerV2 { // Encrypt the confirmation payload with connection's provider public key private static func encryptedData(requestData: SEConfirmAuthorizationRequestData) -> SEEncryptedData? { guard let data = [ - SENetKeys.authorizationCode: requestData.authorizationCode -// ApiConstants.userAuthorizationType: requestData.user, -// ApiConstants.geolocation: requestData.geolocation - // NOTE: Temporarily inactive due to legal restrictions + SENetKeys.authorizationCode: requestData.authorizationCode, + ApiConstants.userAuthorizationType: requestData.authorizationType, + ApiConstants.geolocation: requestData.geolocation ].jsonString else { return nil } return try? SECryptoHelper.encrypt(data, tag: "\(requestData.connectionGuid)_provider_public_key") diff --git a/SaltedgeAuthenticatorSDKv2/README.md b/SaltedgeAuthenticatorSDKv2/README.md index dda05e32..c78575d6 100644 --- a/SaltedgeAuthenticatorSDKv2/README.md +++ b/SaltedgeAuthenticatorSDKv2/README.md @@ -1,6 +1,6 @@ # Authenticator iOS SDK -A client of Salt Edge Authenticator API v2 of Salt Edge SCA Service. Implements Strong Customer Authentication/Dynamic Linking process. +A client of Salt Edge Authenticator API v2 implemeted by Salt Edge SCA Service. Implements Strong Customer Authentication/Dynamic Linking process. ## Requirements @@ -13,9 +13,9 @@ A client of Salt Edge Authenticator API v2 of Salt Edge SCA Service. Implements ### CocoaPods CocoaPods is a dependency manager for Cocoa projects. For usage and installation instructions, visit their website. -To integrate `SaltedgeAuthenticatorSDK` into your Xcode project using CocoaPods, specify it in your Podfile: +To integrate `SaltedgeAuthenticatorSDKv2` into your Xcode project using CocoaPods, specify it in your Podfile: -`pod 'SaltedgeAuthenticatorSDK'` +`pod 'SaltedgeAuthenticatorSDKv2'` ## How to use From 556c5999694e0872922eea3bdef8c29bd1dbe12f Mon Sep 17 00:00:00 2001 From: Daniel Marcenco Date: Fri, 25 Nov 2022 17:03:51 +0200 Subject: [PATCH 110/110] fix location alert --- Example/Authenticator/Utils/Handlers/ConnectHandler.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Example/Authenticator/Utils/Handlers/ConnectHandler.swift b/Example/Authenticator/Utils/Handlers/ConnectHandler.swift index 85f0c5b4..b26804da 100644 --- a/Example/Authenticator/Utils/Handlers/ConnectHandler.swift +++ b/Example/Authenticator/Utils/Handlers/ConnectHandler.swift @@ -75,7 +75,7 @@ final class ConnectHandler { finalString.append(description) delegate?.finishConnectWithSuccess(attributedMessage: finalString) - if connection.geolocationRequired.value != nil { + if connection.geolocationRequired.value != nil && LocationManager.shared.notDeterminedAuthorization { delegate?.requestLocationAuthorization() } }