diff --git a/WultraMobileTokenSDK.xcodeproj/project.pbxproj b/WultraMobileTokenSDK.xcodeproj/project.pbxproj index 06ff6ba..cb7313f 100644 --- a/WultraMobileTokenSDK.xcodeproj/project.pbxproj +++ b/WultraMobileTokenSDK.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 52; objects = { /* Begin PBXBuildFile section */ @@ -72,7 +72,7 @@ EA6DDF1A29F804D60011E234 /* WMTPostApprovalScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA6DDF1929F804D60011E234 /* WMTPostApprovalScreen.swift */; }; EA6DDF1C29F807230011E234 /* OperationUIDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA6DDF1B29F807230011E234 /* OperationUIDataTests.swift */; }; EA9CE2BE2AEAA9FD00FE4E35 /* WMTProximityCheck.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9CE2BD2AEAA9FD00FE4E35 /* WMTProximityCheck.swift */; }; - EA9CE2C22AEBDB0D00FE4E35 /* WMTTOTPUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9CE2C12AEBDB0D00FE4E35 /* WMTTOTPUtils.swift */; }; + EA9CE2C22AEBDB0D00FE4E35 /* WMTPACUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9CE2C12AEBDB0D00FE4E35 /* WMTPACUtils.swift */; }; EAB7054A2AF1161500756AC2 /* TOTPParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB705492AF1161500756AC2 /* TOTPParserTests.swift */; }; EACAF7B02A126B7D0021CA54 /* WMTJsonValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = EACAF7AF2A126B7D0021CA54 /* WMTJsonValue.swift */; }; /* End PBXBuildFile section */ @@ -158,7 +158,7 @@ EA6DDF1929F804D60011E234 /* WMTPostApprovalScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WMTPostApprovalScreen.swift; sourceTree = ""; }; EA6DDF1B29F807230011E234 /* OperationUIDataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationUIDataTests.swift; sourceTree = ""; }; EA9CE2BD2AEAA9FD00FE4E35 /* WMTProximityCheck.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WMTProximityCheck.swift; sourceTree = ""; }; - EA9CE2C12AEBDB0D00FE4E35 /* WMTTOTPUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WMTTOTPUtils.swift; sourceTree = ""; }; + EA9CE2C12AEBDB0D00FE4E35 /* WMTPACUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WMTPACUtils.swift; sourceTree = ""; }; EAB705492AF1161500756AC2 /* TOTPParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TOTPParserTests.swift; sourceTree = ""; }; EACAF7AF2A126B7D0021CA54 /* WMTJsonValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WMTJsonValue.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -295,7 +295,7 @@ DC6E52D4259C959900FC25BE /* Utils */ = { isa = PBXGroup; children = ( - EA9CE2C12AEBDB0D00FE4E35 /* WMTTOTPUtils.swift */, + EA9CE2C12AEBDB0D00FE4E35 /* WMTPACUtils.swift */, DC6E52D5259C964600FC25BE /* WMTOperationExpirationWatcher.swift */, EACAF7AF2A126B7D0021CA54 /* WMTJsonValue.swift */, ); @@ -626,7 +626,7 @@ BFEEB2092937A2680047941D /* WMTInboxGetList.swift in Sources */, BFEEB20729379F960047941D /* WMTInboxSetMessageRead.swift in Sources */, EA44366A29F9294600DDEC1C /* WMTPostApprovaScreenReview.swift in Sources */, - EA9CE2C22AEBDB0D00FE4E35 /* WMTTOTPUtils.swift in Sources */, + EA9CE2C22AEBDB0D00FE4E35 /* WMTPACUtils.swift in Sources */, EA294F3D29F6A07A00A0494E /* WMTOperationUIData.swift in Sources */, DCC5CCB32449F8CD004679AC /* WMTOperationAttribute.swift in Sources */, DCC5CCAC2449F765004679AC /* WMTOperationsImpl.swift in Sources */, diff --git a/WultraMobileTokenSDK/ConfigFiles/Config.xcconfig b/WultraMobileTokenSDK/ConfigFiles/Config.xcconfig index 630288b..409bcfd 100644 --- a/WultraMobileTokenSDK/ConfigFiles/Config.xcconfig +++ b/WultraMobileTokenSDK/ConfigFiles/Config.xcconfig @@ -15,7 +15,7 @@ // // SWIFT -SWIFT_VERSION = 5.7 +SWIFT_VERSION = 5.9 SWIFT_SWIFT3_OBJC_INFERENCE = Off // SDK diff --git a/WultraMobileTokenSDK/Info.plist b/WultraMobileTokenSDK/Info.plist index 6be12c4..90c35c0 100644 --- a/WultraMobileTokenSDK/Info.plist +++ b/WultraMobileTokenSDK/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.5.2 + 1.7.1 CFBundleVersion $(CURRENT_PROJECT_VERSION) diff --git a/WultraMobileTokenSDK/Operations/Utils/WMTPACUtils.swift b/WultraMobileTokenSDK/Operations/Utils/WMTPACUtils.swift new file mode 100644 index 0000000..dad2518 --- /dev/null +++ b/WultraMobileTokenSDK/Operations/Utils/WMTPACUtils.swift @@ -0,0 +1,63 @@ +// +// Copyright 2023 Wultra s.r.o. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions +// and limitations under the License. +// + +import Foundation + +/// Utility class used for handling Proximity Antifraud Check +public class WMTPACUtils { + + /// Method accepts deeplink URL and returns PAC data + public static func parseDeeplink(url: URL) -> WMTPACData? { + + guard let components = URLComponents(string: url.absoluteString) else { + D.error("Failed to get URLComponents: URLString is malformed") + return nil + } + + guard let queryItems = components.queryItems else { + D.error("Failed to get URLComponents queryItems") + return nil + } + + if let operationId = queryItems.first(where: { $0.name == "oid" })?.value { + let totp = queryItems.first(where: { $0.name == "totp" })?.value + return WMTPACData(operationId: operationId, totp: totp) + } else { + D.error("Failed to get operationId from query items") + return nil + } + } + + /// Method accepts scanned code as a String and returns PAC data + public static func parseQRCode(code: String) -> WMTPACData? { + if let encodedURLString = code.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let url = URL(string: encodedURLString) { + return parseDeeplink(url: url) + } else { + D.error("Failed to created URL from QR code String.") + return nil + } + } +} + +/// Data which is return after parsing PAC code +public struct WMTPACData { + + /// The ID of the operation associated with the PAC + public let operationId: String + + /// Time-based one time password used for Proximity antifraud check + public let totp: String? +} diff --git a/WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift b/WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift deleted file mode 100644 index 0ffb766..0000000 --- a/WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift +++ /dev/null @@ -1,91 +0,0 @@ -// -// Copyright 2023 Wultra s.r.o. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions -// and limitations under the License. -// - -import Foundation - -/// Utility class used for handling TOTP -public class WMTTOTPUtils { - - /// Method accepts deeeplink URL and returns payload data - public static func parseDeeplink(url: URL) -> WMTOperationTOTPData? { - - guard let components = URLComponents(string: url.absoluteString) else { - D.error("Failed to get URLComponents: URLString is malformed") - return nil - } - - guard let queryItems = components.queryItems else { - D.error("Failed to get URLComponents queryItems") - return nil - } - - guard let code = queryItems.first?.value else { - D.error("Failed to get Query Items value for parsing") - return nil - } - - guard let data = parseJWT(code: code) else { return nil } - - return data - } - - /// Method accepts scanned code as a String and returns payload data - public static func parseQRCode(code: String) -> WMTOperationTOTPData? { - return parseJWT(code: code) - } - - private static func parseJWT(code: String) -> WMTOperationTOTPData? { - let jwtParts = code.split(separator: ".") - - // At this moment we dont care about header, we want only payload which is the second part of JWT - let jwtBase64String = jwtParts.count > 1 ? String(jwtParts[1]) : "" - - if let base64EncodedData = jwtBase64String.data(using: .utf8), - let dataPayload = Data(base64Encoded: base64EncodedData) { - do { - return try JSONDecoder().decode(WMTOperationTOTPData.self, from: dataPayload) - } catch { - D.error("Failed to decode JWT from: \(code)") - D.error("With error: \(error)") - return nil - } - } - - D.error("Failed to decode QR JWT from: \(jwtBase64String)") - return nil - } -} - -/// Data payload which is returned from JWT parser -public struct WMTOperationTOTPData: Codable { - - /// The actual Time-based one time password - public let totp: String - - /// The ID of the operations associated with the TOTP - public let operationId: String - - public enum Keys: String, CodingKey { - case totp = "totp" - case operationId = "oid" - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: Keys.self) - totp = try container.decode(String.self, forKey: .totp) - operationId = try container.decode(String.self, forKey: .operationId) - } -} diff --git a/WultraMobileTokenSDKTests/TOTPParserTests.swift b/WultraMobileTokenSDKTests/TOTPParserTests.swift index 586de14..feddc8a 100644 --- a/WultraMobileTokenSDKTests/TOTPParserTests.swift +++ b/WultraMobileTokenSDKTests/TOTPParserTests.swift @@ -22,38 +22,38 @@ final class TOTPParserTest: XCTestCase { func testQRTOTPParserWithEmptyCode() { let code = "" - XCTAssertNil(WMTTOTPUtils.parseQRCode(code: code)) + XCTAssertNil(WMTPACUtils.parseQRCode(code: code)) } func testQRTOTPParserWithShortCode() { let code = "abc" - XCTAssertNil(WMTTOTPUtils.parseQRCode(code: code)) + XCTAssertNil(WMTPACUtils.parseQRCode(code: code)) } func testQRTOTPParserWithValidCode() { let code = "eyJhbGciOiJub25lIiwidHlwZSI6IkpXVCJ9.eyJvaWQiOiI2YTFjYjAwNy1mZjc1LTRmNDAtYTIxYi0wYjU0NmYwZjZjYWQiLCJ0b3RwIjoiNzM3NDMxOTQifQ==" - XCTAssertEqual(WMTTOTPUtils.parseQRCode(code: code)?.totp, "73743194", "Parsing of totp failed") - XCTAssertEqual(WMTTOTPUtils.parseQRCode(code: code)?.operationId, "6a1cb007-ff75-4f40-a21b-0b546f0f6cad", "Parsing of operationId failed") + XCTAssertEqual(WMTPACUtils.parseQRCode(code: code)?.totp, "73743194", "Parsing of totp failed") + XCTAssertEqual(WMTPACUtils.parseQRCode(code: code)?.operationId, "6a1cb007-ff75-4f40-a21b-0b546f0f6cad", "Parsing of operationId failed") } func testDeeplinkTOTPParserWithInvalidURL() { let url = URL(string: "mtoken://an-invalid-url.com")! - XCTAssertNil(WMTTOTPUtils.parseDeeplink(url: url)) + XCTAssertNil(WMTPACUtils.parseDeeplink(url: url)) } func testDeeplinkTOTPParserWithInvalidJWTCode() { let url = URL(string: "mtoken://login?code=abc")! - XCTAssertNil(WMTTOTPUtils.parseDeeplink(url: url)) + XCTAssertNil(WMTPACUtils.parseDeeplink(url: url)) } func testDeeplinkTOTPParserWithValidJWTCode() { let url = URL(string: "mtoken://login?code=eyJhbGciOiJub25lIiwidHlwZSI6IkpXVCJ9.eyJvaWQiOiJkZjYxMjhmYy1jYTUxLTQ0YjctYmVmYS1jYTBlMTQwOGFhNjMiLCJ0b3RwIjoiNTY3MjU0OTQifQ==")! - XCTAssertEqual(WMTTOTPUtils.parseDeeplink(url: url)?.totp, "56725494", "Parsing of totp failed") - XCTAssertEqual(WMTTOTPUtils.parseDeeplink(url: url)?.operationId, "df6128fc-ca51-44b7-befa-ca0e1408aa63", "Parsing of operationId failed") + XCTAssertEqual(WMTPACUtils.parseDeeplink(url: url)?.totp, "56725494", "Parsing of totp failed") + XCTAssertEqual(WMTPACUtils.parseDeeplink(url: url)?.operationId, "df6128fc-ca51-44b7-befa-ca0e1408aa63", "Parsing of operationId failed") } }