diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1ae878d..c81f9fe 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -28,5 +28,4 @@ jobs: OP_URL: ${{ secrets.TESTS_OP_URL }} ER_URL: ${{ secrets.TESTS_ER_URL }} IN_URL: ${{ secrets.TESTS_IN_URL }} - PU_URL: ${{ secrets.TESTS_PU_URL }} - run: ./scripts/test.sh -destination "platform=iOS Simulator,OS=17.0.1,name=iPhone 15" -sdkconfig "$SDK_CONFIG" -er "$ER_URL" -op "$OP_URL" -in "$IN_URL" -cl "$CL_URL" -clu "$CL_LGN" -clp "$CL_PWD" -cla "$CL_AID" -pu "$PU_URL" \ No newline at end of file + run: ./scripts/test.sh -destination "platform=iOS Simulator,OS=17.0.1,name=iPhone 15" -sdkconfig "$SDK_CONFIG" -er "$ER_URL" -op "$OP_URL" -in "$IN_URL" -cl "$CL_URL" -clu "$CL_LGN" -clp "$CL_PWD" -cla "$CL_AID" \ No newline at end of file diff --git a/WultraMobileTokenSDK.xcodeproj/project.pbxproj b/WultraMobileTokenSDK.xcodeproj/project.pbxproj index e8b2f11..d78a9f3 100644 --- a/WultraMobileTokenSDK.xcodeproj/project.pbxproj +++ b/WultraMobileTokenSDK.xcodeproj/project.pbxproj @@ -44,9 +44,6 @@ DCA43C6D2993F63E0059A163 /* WMTOperationAttributeImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA43C6C2993F63E0059A163 /* WMTOperationAttributeImage.swift */; }; DCAB7BC824580B4C0006989D /* WMTQROperationParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAB7BC724580B4C0006989D /* WMTQROperationParser.swift */; }; DCAB7BCA24580BAC0006989D /* WMTQROperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAB7BC924580BAC0006989D /* WMTQROperation.swift */; }; - DCAC55992CE68C2A0070644A /* ProvisioningUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAC55982CE68C2A0070644A /* ProvisioningUtilsTests.swift */; }; - DCAC559C2CE773E90070644A /* WMTProvisioningUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAC559B2CE773E90070644A /* WMTProvisioningUtils.swift */; }; - DCAC55BC2CEC954C0070644A /* WMTUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAC55BB2CEC954C0070644A /* WMTUtils.swift */; }; DCC3420424E3DB310045D27D /* WMTPushParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCC3420324E3DB310045D27D /* WMTPushParser.swift */; }; DCC5CC9F2449EE21004679AC /* MobileTokenSDK.h in Headers */ = {isa = PBXBuildFile; fileRef = DCC5CC9D2449EE21004679AC /* MobileTokenSDK.h */; settings = {ATTRIBUTES = (Public, ); }; }; DCC5CCAC2449F765004679AC /* WMTOperationsImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCC5CCAB2449F765004679AC /* WMTOperationsImpl.swift */; }; @@ -66,8 +63,6 @@ DCD8B336246C1BAF00385F02 /* WMTRejectionReason.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCD8B335246C1BAF00385F02 /* WMTRejectionReason.swift */; }; DCE660D124CEBECA00870E53 /* IntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE660D024CEBECA00870E53 /* IntegrationTests.swift */; }; DCE660D324CEF56400870E53 /* IntegrationProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE660D224CEF56400870E53 /* IntegrationProxy.swift */; }; - DCE6D5742CF5F46000865D6E /* WMTSignatureAPNSEnvironmentDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE6D5732CF5F46000865D6E /* WMTSignatureAPNSEnvironmentDetector.swift */; }; - DCE6D5772CF5F5D500865D6E /* WMTMachOReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE6D5762CF5F5D500865D6E /* WMTMachOReader.swift */; }; EA294F3D29F6A07A00A0494E /* WMTOperationUIData.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA294F3C29F6A07A00A0494E /* WMTOperationUIData.swift */; }; EA44366A29F9294600DDEC1C /* WMTPostApprovaScreenReview.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA44366929F9294600DDEC1C /* WMTPostApprovaScreenReview.swift */; }; EA44366C29F9297100DDEC1C /* WMTPostApprovaScreenRedirect.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA44366B29F9297100DDEC1C /* WMTPostApprovaScreenRedirect.swift */; }; @@ -131,9 +126,6 @@ DCA43C6C2993F63E0059A163 /* WMTOperationAttributeImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WMTOperationAttributeImage.swift; sourceTree = ""; }; DCAB7BC724580B4C0006989D /* WMTQROperationParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WMTQROperationParser.swift; sourceTree = ""; }; DCAB7BC924580BAC0006989D /* WMTQROperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WMTQROperation.swift; sourceTree = ""; }; - DCAC55982CE68C2A0070644A /* ProvisioningUtilsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProvisioningUtilsTests.swift; sourceTree = ""; }; - DCAC559B2CE773E90070644A /* WMTProvisioningUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WMTProvisioningUtils.swift; sourceTree = ""; }; - DCAC55BB2CEC954C0070644A /* WMTUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WMTUtils.swift; sourceTree = ""; }; DCC3420324E3DB310045D27D /* WMTPushParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WMTPushParser.swift; sourceTree = ""; }; DCC5CC9A2449EE21004679AC /* WultraMobileTokenSDK.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = WultraMobileTokenSDK.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DCC5CC9D2449EE21004679AC /* MobileTokenSDK.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MobileTokenSDK.h; sourceTree = ""; }; @@ -158,8 +150,6 @@ DCD8B335246C1BAF00385F02 /* WMTRejectionReason.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WMTRejectionReason.swift; sourceTree = ""; }; DCE660D024CEBECA00870E53 /* IntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegrationTests.swift; sourceTree = ""; }; DCE660D224CEF56400870E53 /* IntegrationProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegrationProxy.swift; sourceTree = ""; }; - DCE6D5732CF5F46000865D6E /* WMTSignatureAPNSEnvironmentDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WMTSignatureAPNSEnvironmentDetector.swift; sourceTree = ""; }; - DCE6D5762CF5F5D500865D6E /* WMTMachOReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WMTMachOReader.swift; sourceTree = ""; }; EA294F3C29F6A07A00A0494E /* WMTOperationUIData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WMTOperationUIData.swift; sourceTree = ""; }; EA44366929F9294600DDEC1C /* WMTPostApprovaScreenReview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WMTPostApprovaScreenReview.swift; sourceTree = ""; }; EA44366B29F9297100DDEC1C /* WMTPostApprovaScreenRedirect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WMTPostApprovaScreenRedirect.swift; sourceTree = ""; }; @@ -246,16 +236,6 @@ path = UserOperation; sourceTree = ""; }; - DC3E529E2CF62891002621C1 /* ProvisioningUtils */ = { - isa = PBXGroup; - children = ( - DCAC559B2CE773E90070644A /* WMTProvisioningUtils.swift */, - DCE6D5732CF5F46000865D6E /* WMTSignatureAPNSEnvironmentDetector.swift */, - DCE6D5762CF5F5D500865D6E /* WMTMachOReader.swift */, - ); - path = ProvisioningUtils; - sourceTree = ""; - }; DC488034292282FF00DB844B /* Inbox */ = { isa = PBXGroup; children = ( @@ -297,7 +277,6 @@ DCE660D024CEBECA00870E53 /* IntegrationTests.swift */, DCE660D224CEF56400870E53 /* IntegrationProxy.swift */, DC395C0924E55B9B0007C36E /* PushParserTests.swift */, - DCAC55982CE68C2A0070644A /* ProvisioningUtilsTests.swift */, DC6EDB7825A49ED900A229E4 /* OperationExpirationTests.swift */, DC616235248508F8000DED17 /* QROperationParserTests.swift */, EAB705492AF1161500756AC2 /* PACParserTests.swift */, @@ -377,13 +356,11 @@ DC81D1CE24502E0300F80CD6 /* Common */ = { isa = PBXGroup; children = ( - DC3E529E2CF62891002621C1 /* ProvisioningUtils */, BFEEB20A2937AD700047941D /* WMTCancellable.swift */, DC488030292282C900DB844B /* WMTService.swift */, DCC5CCCD244DB0AD004679AC /* WMTLogger.swift */, DC06D01E25AC74E400F2EA69 /* WMTLock.swift */, DC9511F826EA02C100FF40AD /* WPNIntegration.swift */, - DCAC55BB2CEC954C0070644A /* WMTUtils.swift */, ); path = Common; sourceTree = ""; @@ -611,7 +588,6 @@ files = ( DC61624224852B6D000DED17 /* NetworkingObjectsTests.swift in Sources */, DC395C0A24E55B9B0007C36E /* PushParserTests.swift in Sources */, - DCAC55992CE68C2A0070644A /* ProvisioningUtilsTests.swift in Sources */, DC6EDB7925A49ED900A229E4 /* OperationExpirationTests.swift in Sources */, EAB7054A2AF1161500756AC2 /* PACParserTests.swift in Sources */, DC616236248508F8000DED17 /* QROperationParserTests.swift in Sources */, @@ -634,7 +610,6 @@ DC3D0B392480F886000DC4D9 /* WMTLocalOperation.swift in Sources */, DCD8B336246C1BAF00385F02 /* WMTRejectionReason.swift in Sources */, DCC5CCD8244DBBBD004679AC /* WMTAuthorizationData.swift in Sources */, - DCAC55BC2CEC954C0070644A /* WMTUtils.swift in Sources */, DC488040292282FF00DB844B /* WMTInboxCount.swift in Sources */, DCA43C6B29927C960059A163 /* WMTOperationAttributeAmountConversion.swift in Sources */, EA74F7B32C2561BB004340B9 /* WMTResultTexts.swift in Sources */, @@ -664,15 +639,12 @@ DCC5CCCE244DB0AD004679AC /* WMTLogger.swift in Sources */, DCC5CCAE2449F7AC004679AC /* WMTUserOperation.swift in Sources */, DC9511F926EA02C100FF40AD /* WPNIntegration.swift in Sources */, - DCE6D5772CF5F5D500865D6E /* WMTMachOReader.swift in Sources */, DCC5CCBD2449F965004679AC /* WMTOperationAttributeHeading.swift in Sources */, - DCAC559C2CE773E90070644A /* WMTProvisioningUtils.swift in Sources */, DC8CB206244DD007009DDAA3 /* WMTAllowedOperationSignature.swift in Sources */, DCC3420424E3DB310045D27D /* WMTPushParser.swift in Sources */, BFEEB20529379C700047941D /* WMTInboxGetMessageDetail.swift in Sources */, EACAF7B02A126B7D0021CA54 /* WMTJsonValue.swift in Sources */, DCAB7BCA24580BAC0006989D /* WMTQROperation.swift in Sources */, - DCE6D5742CF5F46000865D6E /* WMTSignatureAPNSEnvironmentDetector.swift in Sources */, DCC5CCBF2449F981004679AC /* WMTOperationAttributePartyInfo.swift in Sources */, DC81D1CB244F451E00F80CD6 /* WMTPushImpl.swift in Sources */, EA9CE2BE2AEAA9FD00FE4E35 /* WMTProximityCheck.swift in Sources */, diff --git a/WultraMobileTokenSDK/Common/ProvisioningUtils/WMTMachOReader.swift b/WultraMobileTokenSDK/Common/ProvisioningUtils/WMTMachOReader.swift deleted file mode 100644 index 35f92ef..0000000 --- a/WultraMobileTokenSDK/Common/ProvisioningUtils/WMTMachOReader.swift +++ /dev/null @@ -1,185 +0,0 @@ -// -// Copyright 2024 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 -import MachO -import CommonCrypto - -class WMTMachOReader { - - private struct CSSuperBlob { - var magic: UInt32 - var length: UInt32 - var count: UInt32 - } - - private struct CSBlob { - var type: UInt32 - var offset: UInt32 - } - - private struct CSMagic { - static let embeddedSignature: UInt32 = 0xfade0cc0 - static let embeddedEntitlements: UInt32 = 0xfade7171 - } - - private enum BinaryType { - struct HeaderData { - let headerSize: Int - let commandCount: Int - } - struct FatHeaderData { - let archCount: Int - } - case singleArch(headerInfo: HeaderData) - case fat(header: FatHeaderData) - } - - private var entitlements = [WMTProvision.Entitlements]() - - static func readEntitlements(_ binaryPath: String) -> [WMTProvision.Entitlements]? { - WMTMachOReader(binaryPath)?.entitlements - } - - private init?(_ binaryPath: String) { - guard let binary = BinaryReader(binaryPath) else { - return nil - } - - switch getBinaryType(binary: binary) { - case .singleArch(let headerInfo): - let headerSize = headerInfo.headerSize - let commandCount = headerInfo.commandCount - if let data = readEntitlementsFromBinarySlice(binary: binary, headerOffset: headerSize, dataOffset: 0, cmdCount: commandCount) { - entitlements.append(data) - } - case .fat(let header): - entitlements.append(contentsOf: readEntitlementsFromFatBinary(binary: binary, architectureCount: header.archCount, startingAt: MemoryLayout.size)) - default: - return nil - } - } - - private func getBinaryType(binary: BinaryReader, fromSliceStartingAt offset: UInt64 = 0) -> BinaryType? { - binary.seek(to: offset) - let header: mach_header = binary.read() - let commandCount = Int(header.ncmds) - switch header.magic { - case MH_MAGIC: - let data = BinaryType.HeaderData(headerSize: MemoryLayout.size, commandCount: commandCount) - return .singleArch(headerInfo: data) - case MH_MAGIC_64: - let data = BinaryType.HeaderData(headerSize: MemoryLayout.size, commandCount: commandCount) - return .singleArch(headerInfo: data) - default: - binary.seek(to: 0) - let fatHeader: fat_header = binary.read() - if CFSwapInt32(fatHeader.magic) == FAT_MAGIC { - let archCount = Int(CFSwapInt32(fatHeader.nfat_arch)) - return .fat(header: BinaryType.FatHeaderData(archCount: archCount)) - } else { - return nil - } - } - } - - private func readEntitlementsFromFatBinary(binary: BinaryReader, architectureCount: Int, startingAt: Int) -> [WMTProvision.Entitlements] { - var entitlements = [WMTProvision.Entitlements]() - for i in 0...size) - binary.seek(to: UInt64(offset)) - let fatArch: fat_arch = binary.read() - let fatArchOffset = CFSwapInt32(fatArch.offset) - let arch = getBinaryType(binary: binary, fromSliceStartingAt: UInt64(fatArchOffset)) - switch arch { - case .singleArch(let headerInfo): - let headerOffset = Int(fatArchOffset) + headerInfo.headerSize - if let parsed = readEntitlementsFromBinarySlice(binary: binary, headerOffset: headerOffset, dataOffset: fatArchOffset, cmdCount: headerInfo.commandCount) { - entitlements.append(parsed) - } - default: - break - } - } - return entitlements - } - - private func readEntitlementsFromBinarySlice(binary: BinaryReader, headerOffset: Int, dataOffset: UInt32, cmdCount: Int) -> WMTProvision.Entitlements? { - binary.seek(to: UInt64(headerOffset)) - for _ in 0...size))) - } - return nil - } - - private func readEntitlementsData(binary: BinaryReader, startingAt offset: UInt32) -> WMTProvision.Entitlements? { - binary.seek(to: UInt64(offset)) - let metaBlob: CSSuperBlob = binary.read() - if CFSwapInt32(metaBlob.magic) == CSMagic.embeddedSignature { - let metaBlobSize = UInt32(MemoryLayout.size) - let blobSize = UInt32(MemoryLayout.size) - let itemCount = CFSwapInt32(metaBlob.count) - for index in 0..() -> T { - handle.readData(ofLength: MemoryLayout.size).withUnsafeBytes({ $0.load(as: T.self) }) - } - - func readData(ofLength length: Int) -> Data { - handle.readData(ofLength: length) - } - - deinit { - handle.closeFile() - } -} diff --git a/WultraMobileTokenSDK/Common/ProvisioningUtils/WMTProvisioningUtils.swift b/WultraMobileTokenSDK/Common/ProvisioningUtils/WMTProvisioningUtils.swift deleted file mode 100644 index 84ebcd1..0000000 --- a/WultraMobileTokenSDK/Common/ProvisioningUtils/WMTProvisioningUtils.swift +++ /dev/null @@ -1,137 +0,0 @@ -// -// Copyright 2024 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 - -class WMTProvisioningUtils { - - static func getMainProvisioningProfile() -> WMTProvision? { - D.debug("Retrieving embedded provisioning profile from the main bundle.") - guard let filePath = Bundle.main.path(forResource: "embedded", ofType: "mobileprovision") else { - D.error("Missing embedded provisioning profile in the main bundle.") - return nil - } - let url = URL(fileURLWithPath: filePath) - do { - let data = try Data(contentsOf: url) - return getProvisioningProfileFromData(data) - } catch let e { - D.error("Failed to load provisioning profile: \(e)") - return nil - } - } - - static func getProvisioningProfileFromData(_ profile: Data) -> WMTProvision? { - guard let string = String(data: profile, encoding: .isoLatin1) else { - D.error("Failed to decode provisioning profile data in ISO Latin 1.") - return nil - } - let scanner = Scanner(string: string as String) - guard scanner.scanUpTo("", into: &extractedPlist) != false else { - D.error("Search for provisioning profile plist end tag failed.") - return nil - } - - guard let plist = extractedPlist?.appending("").data(using: .isoLatin1) else { - D.error("Failed to convert provisioning profile plist to data.") - return nil - } - return parseProvisioningProfilePlist(plist) - } - - static func parseProvisioningProfilePlist(_ plist: Data) -> WMTProvision? { - do { - let provision = try PropertyListDecoder().decode(WMTProvision.self, from: plist) - D.debug("Successfully parsed provisioning profile (apns env: \(provision.entitlements.apsEnvironment?.rawValue ?? "nil")).") - return provision - } catch let e { - D.error("Failed to parse provisioning profile: \(e)") - return nil - } - } -} - -/// Provisioning profile plist structure. Note that we need only entitlements for now. -/// The rest of the struct is commented out in case something changes so it does not break the parser unnecesarilly -struct WMTProvision: Decodable { - /* - var name: String - var appIDName: String - var platform: [String] - var isXcodeManaged: Bool? = false - var creationDate: Date - var expirationDate: Date - */ - var entitlements: Entitlements - - private enum CodingKeys: String, CodingKey { - /* - case name = "Name" - case appIDName = "AppIDName" - case platform = "Platform" - case isXcodeManaged = "IsXcodeManaged" - case creationDate = "CreationDate" - case expirationDate = "ExpirationDate" - */ - case entitlements = "Entitlements" - } - - struct Entitlements: Decodable { - /* - let keychainAccessGroups: [String] - let getTaskAllow: Bool - */ - let apsEnvironment: Environment? - - private enum CodingKeys: String, CodingKey { - /* - case keychainAccessGroups = "keychain-access-groups" - case getTaskAllow = "get-task-allow" - */ - case apsEnvironment = "aps-environment" - } - - enum Environment: String, Decodable { - case development - case production - } - - init(/*keychainAccessGroups: [String], getTaskAllow: Bool,*/ apsEnvironment: Environment?) { - /* - self.keychainAccessGroups = keychainAccessGroups - self.getTaskAllow = getTaskAllow - */ - self.apsEnvironment = apsEnvironment - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - /* - let keychainAccessGroups: [String] = (try? container.decode([String].self, forKey: .keychainAccessGroups)) ?? [] - let getTaskAllow: Bool = (try? container.decode(Bool.self, forKey: .getTaskAllow)) ?? false - */ - let apsEnvironment = try? container.decode(Environment.self, forKey: .apsEnvironment) - - self.init(/*keychainAccessGroups: keychainAccessGroups, getTaskAllow: getTaskAllow,*/ apsEnvironment: apsEnvironment) - } - } -} diff --git a/WultraMobileTokenSDK/Common/ProvisioningUtils/WMTSignatureAPNSEnvironmentDetector.swift b/WultraMobileTokenSDK/Common/ProvisioningUtils/WMTSignatureAPNSEnvironmentDetector.swift deleted file mode 100644 index a788324..0000000 --- a/WultraMobileTokenSDK/Common/ProvisioningUtils/WMTSignatureAPNSEnvironmentDetector.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// Copyright 2024 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 - -class WMTSignatureAPNSEnvironmentDetector { - - static func detectAPNSEnvironment() -> WMTProvision.Entitlements? { - - D.debug("Parsing main bundle signature to get APNS Environment.") - - guard let executableName = Bundle.main.infoDictionary?[kCFBundleExecutableKey as String] as? String else { - D.error("Could not read executable name from Info.plist") - return nil - } - guard let executablePath = Bundle.main.path(forResource: executableName, ofType: nil) else { - D.error("Could not find executable \(executableName)") - return nil - } - - guard let entitlements = WMTMachOReader.readEntitlements(executablePath) else { - D.error("Could not read entitlements from \(executablePath)") - return nil - } - - guard entitlements.isEmpty == false else { - D.info("Not entitlements found") - return nil - } - - return entitlements.first(where: { $0.apsEnvironment != nil }) - } -} diff --git a/WultraMobileTokenSDK/Common/WMTUtils.swift b/WultraMobileTokenSDK/Common/WMTUtils.swift deleted file mode 100644 index e77b8f8..0000000 --- a/WultraMobileTokenSDK/Common/WMTUtils.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// Copyright 2024 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 - -internal extension Data { - - static let toHexTable: [Character] = [ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F" ] - - func toHex() -> String { - var result = "" - result.reserveCapacity(count * 2) - for byte in self { - let byteAsUInt = Int(byte) - result.append(Self.toHexTable[byteAsUInt >> 4]) - result.append(Self.toHexTable[byteAsUInt & 15]) - } - return result - } -} diff --git a/WultraMobileTokenSDK/Push/Model/WMTPushRegistrationData.swift b/WultraMobileTokenSDK/Push/Model/WMTPushRegistrationData.swift index cc18379..d7a041e 100644 --- a/WultraMobileTokenSDK/Push/Model/WMTPushRegistrationData.swift +++ b/WultraMobileTokenSDK/Push/Model/WMTPushRegistrationData.swift @@ -18,36 +18,11 @@ import Foundation class WMTPushRegistrationData: Codable { - let platform: WMTPushRegistrationPlatform + let platform: String let token: String - let environment: WMTPushRegistrationEnvironment? - init(platform: WMTPushRegistrationPlatform, token: String, environment: WMTPushRegistrationEnvironment?) { - self.platform = platform - self.token = token - self.environment = environment - } -} - -enum WMTPushRegistrationPlatform: String, Codable { - case ios // for backwards compatibility - case apns - case fcm - // case hms - Huawei Messaging Service not available for iOS -} - -enum WMTPushRegistrationEnvironment: String, Codable { - case production - case development -} - -extension WMTProvision.Entitlements.Environment { - var serverObject: WMTPushRegistrationEnvironment { - switch self { - case .development: - return .development - case .production: - return .production - } + init(token: String) { + self.platform = "ios" + self.token = token } } diff --git a/WultraMobileTokenSDK/Push/Service/WMTPushImpl.swift b/WultraMobileTokenSDK/Push/Service/WMTPushImpl.swift index a460d05..3fe5086 100644 --- a/WultraMobileTokenSDK/Push/Service/WMTPushImpl.swift +++ b/WultraMobileTokenSDK/Push/Service/WMTPushImpl.swift @@ -62,37 +62,6 @@ class WMTPushImpl: WMTPush, WMTService { @discardableResult func registerDeviceTokenForPushNotifications(token: Data, completion: @escaping (Result) -> Void) -> Operation? { - // ios for backwards compatibility - return registerPush( - platform: .ios, - token: token.toHex(), - environment: getPushEnvironment(environment: .automatic), - completion: completion - ) - } - - @discardableResult - func register(to platform: WMTPushPlatform, completion: @escaping (Result) -> Void) -> Operation? { - - let payloadPlatform: WMTPushRegistrationPlatform - let payloadToken = platform.token - let payloadEnvironment: WMTPushRegistrationEnvironment? - - switch platform { - case .apns(_, let environment): - payloadPlatform = .apns - payloadEnvironment = getPushEnvironment(environment: environment) - case .fcm: - payloadPlatform = .fcm - payloadEnvironment = nil // no env for FCM - } - - D.info("Registering push for \(payloadPlatform.rawValue) platform.") - - return registerPush(platform: payloadPlatform, token: payloadToken, environment: payloadEnvironment, completion: completion) - } - - private func registerPush(platform: WMTPushRegistrationPlatform, token: String, environment: WMTPushRegistrationEnvironment?, completion: @escaping (Result) -> Void) -> Operation? { guard validateActivation(completion) else { return nil @@ -108,7 +77,7 @@ class WMTPushImpl: WMTPush, WMTService { pendingRegistrationForRemotePushNotifications = true pushNotificationsRegisteredOnServer = false - let data = WMTPushRegistrationData(platform: platform, token: token, environment: environment) + let data = WMTPushRegistrationData(token: HexadecimalString.encodeData(token)) return networking.post(data: .init(data), signedWith: .possession(), to: WMTPushEndpoints.RegisterDevice.endpoint) { _, error in self.pendingRegistrationForRemotePushNotifications = false @@ -121,32 +90,21 @@ class WMTPushImpl: WMTPush, WMTService { } } } - - private func getPushEnvironment(environment: WMTPushAPNSEnvironment) -> WMTPushRegistrationEnvironment? { - switch environment { - case .development: - D.info("Using APNS development environment for push notifications.") - return .development - case .production: - D.info("Using APNS production environment for push notifications.") - return .production - case .automatic: - let env = WMTProvisioningUtils.getMainProvisioningProfile()?.entitlements.apsEnvironment ?? WMTSignatureAPNSEnvironmentDetector.detectAPNSEnvironment()?.apsEnvironment - if let env { - D.info("Using \(env) environment for push notifications (automatic resolution).") - } else { - D.warning("No APNS environment found in provisioning profile. Server configuration will be used.") - } - return env?.serverObject - } - } } -extension WMTPushPlatform { - var token: String { - return switch self { - case .apns(token: let token, environment: _): token.toHex() - case .fcm(token: let token): token +private class HexadecimalString { + + static let toHexTable: [Character] = [ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F" ] + + static func encodeData(_ data: Data) -> String { + var result = "" + result.reserveCapacity(data.count * 2) + for byte in data { + let byteAsUInt = Int(byte) + result.append(toHexTable[byteAsUInt >> 4]) + result.append(toHexTable[byteAsUInt & 15]) } + return result } + } diff --git a/WultraMobileTokenSDK/Push/WMTPush.swift b/WultraMobileTokenSDK/Push/WMTPush.swift index 37a6f46..28bc229 100644 --- a/WultraMobileTokenSDK/Push/WMTPush.swift +++ b/WultraMobileTokenSDK/Push/WMTPush.swift @@ -29,54 +29,11 @@ public protocol WMTPush: AnyObject { /// Registers the current powerauth activation for push notifications. /// - /// This method is compatible with server stack `1.9.x` - /// /// - Parameters: /// - token: Push token. /// - completion: Completion handler. /// This completion is always called on the main thread. /// - Returns: Operation object for its state observation. @discardableResult - @available(*, deprecated, renamed: "register", message: "This method is deprecated since server version 1.10.0. Use register(token:completion:) instead.") func registerDeviceTokenForPushNotifications(token: Data, completion: @escaping (Result) -> Void) -> Operation? - - /// Registers the current powerauth activation for push notifications. - /// - /// - Parameters: - /// - platform: Platform that you're registering to - /// - completion: Completion handler. - /// This completion is always called on the main thread. - /// - Returns: Operation object for its state observation. - @discardableResult - func register(to platform: WMTPushPlatform, completion: @escaping (Result) -> Void) -> Operation? -} - -/// Push platform that is used for push notifications -public enum WMTPushPlatform { - - /// Apple Push Notification Service - when you're using directly Apple Push Service for push notifications - /// - Parameters: - /// - token: APNS push token data retrieved from the system - /// - environment: APNS push environment. Default value is `automatic` - case apns(token: Data, environment: WMTPushAPNSEnvironment = .automatic) - - /// Firebase Cloud Messaging - when you're using Firebase to send push notifications - /// - Parameters: - /// - token: FCM token retrieved from the Firebase SDK - case fcm(token: String) -} - -/// APNS push environment. -/// -/// Production environment (server) must be used for production signed apps. (For example TestFlight or AppStore distrubution). -/// Development environment (server) must be used for developer-signed apps. (For example debug builds or ad-hoc development distrubition). -public enum WMTPushAPNSEnvironment { - /// Automatically detect how is the app signed and set the environment properly. - /// - /// When automatic detection fails, environment is not sent at all and server configuration is used. - case automatic - /// Production APNS environment for production-signed app (TestFlight and AppStore distribution). - case production - /// Development signed app. - case development } diff --git a/WultraMobileTokenSDKTests/Configs/Readme.md b/WultraMobileTokenSDKTests/Configs/Readme.md index fe83a36..701248a 100644 --- a/WultraMobileTokenSDKTests/Configs/Readme.md +++ b/WultraMobileTokenSDKTests/Configs/Readme.md @@ -11,7 +11,6 @@ _Example config:_ "enrollmentServerUrl" : "https://url-to-my-cloud.com/enrollment-server", "operationsServerUrl" : "https://url-to-my-cloud.com/enrollment-server", "inboxServerUrl" : "https://url-to-my-cloud.com/enrollment-server", - "pushServerUrl" : "https://url-to-my-cloud.com/enrollment-server", "sdkConfig" : "PB5YVmaON739UFGyBfog274wr3EJdcMLzVikqJMKOoPz+O11+45YGIPq+2z8L0p43LC/IVKrViJ+v1SHc1/PwPrtCsCQ5FX4fOFOJEFZZPLFs=" } ``` diff --git a/WultraMobileTokenSDKTests/IntegrationProxy.swift b/WultraMobileTokenSDKTests/IntegrationProxy.swift index 3fef384..b0497df 100644 --- a/WultraMobileTokenSDKTests/IntegrationProxy.swift +++ b/WultraMobileTokenSDKTests/IntegrationProxy.swift @@ -23,7 +23,6 @@ class IntegrationProxy { private(set) var powerAuth: PowerAuthSDK? private(set) var operations: WMTOperations? private(set) var inbox: WMTInbox? - private(set) var push: WMTPush? private var config: IntegrationConfig! private let activationName = UUID().uuidString @@ -31,11 +30,10 @@ class IntegrationProxy { typealias Callback = (_ error: String?) -> Void - func prepareActivation(pin: String, configFileName: String = "config", callback: @escaping Callback) { + func prepareActivation(pin: String, callback: @escaping Callback) { WPNLogger.verboseLevel = .debug - - guard let configPath = Bundle.init(for: IntegrationProxy.self).path(forResource: configFileName, ofType: "json", inDirectory: "Configs") else { - callback("Config file \(configFileName).json is not present.") + guard let configPath = Bundle.init(for: IntegrationProxy.self).path(forResource: "config", ofType: "json", inDirectory: "Configs") else { + callback("Config file config.json is not present.") return } @@ -43,7 +41,7 @@ class IntegrationProxy { let configContent = try String(contentsOfFile: configPath) config = try JSONDecoder().decode(IntegrationConfig.self, from: configContent.data(using: .utf8)!) } catch _ { - callback("Config file \(configFileName).json cannot be parsed.") + callback("Config file config.json cannot be parsed.") return } @@ -54,11 +52,9 @@ class IntegrationProxy { } else { let wpnOperationsConf = WPNConfig(baseUrl: URL(string: self.config.operationsServerUrl)!, sslValidation: .noValidation) let wpnInboxConf = WPNConfig(baseUrl: URL(string: self.config.inboxServerUrl)!, sslValidation: .noValidation) - let wpnPushConf = WPNConfig(baseUrl: URL(string: self.config.pushServerUrl)!, sslValidation: .noValidation) self.powerAuth = pa self.operations = pa.createWMTOperations(networkingConfig: wpnOperationsConf, pollingOptions: [.pauseWhenOnBackground]) self.inbox = pa.createWMTInbox(networkingConfig: wpnInboxConf) - self.push = pa.createWMTPush(networkingConfig: wpnPushConf) callback(nil) } } @@ -306,7 +302,6 @@ private struct IntegrationConfig: Codable { let enrollmentServerUrl: String let operationsServerUrl: String let inboxServerUrl: String - let pushServerUrl: String let sdkConfig: String } diff --git a/WultraMobileTokenSDKTests/IntegrationTests.swift b/WultraMobileTokenSDKTests/IntegrationTests.swift index 07341be..a38e71c 100644 --- a/WultraMobileTokenSDKTests/IntegrationTests.swift +++ b/WultraMobileTokenSDKTests/IntegrationTests.swift @@ -18,6 +18,7 @@ import XCTest import PowerAuth2 @testable import WultraMobileTokenSDK + /** For integration test to be successfully executed, you need to provide configuration json file. To more information, visit `WultraMobileTokenSDKTests/Configs/Readme.md`. @@ -29,7 +30,6 @@ class IntegrationTests: XCTestCase { private var pa: PowerAuthSDK! { proxy.powerAuth } private var ops: WMTOperations! { proxy.operations } private var inbox: WMTInbox! { proxy.inbox } - private var push: WMTPush! { proxy.push} private let pin = "1234" @@ -42,7 +42,7 @@ class IntegrationTests: XCTestCase { // Integration Utils prepares an valid activation and sets is as primary // token activation on nextstep server - proxy.prepareActivation(pin: pin/*, configFileName: "config-stable"*/) { error in + proxy.prepareActivation(pin: pin) { error in if let error = error { XCTFail(error) } @@ -138,6 +138,37 @@ class IntegrationTests: XCTestCase { waitForExpectations(timeout: 20, handler: nil) } + /// Test of the Operation cancel + func testDetailCancel() { + let exp = expectation(description: "Cancel operation detail") + + proxy.createNonPersonalisedPACOperation { op in + if let op { + DispatchQueue.main.async { + guard let operation = self.ops.getDetail(operationId: op.operationId, completion: { _ in + XCTFail("Operation should be already canceled") + exp.fulfill() + }) else { + XCTFail("Failed to create operation") + exp.fulfill() + return + } + + operation.cancel() + + // Allowing most of the timeout duration for potential completion of the getDetail call. + DispatchQueue.main.asyncAfter(deadline: .now() + 4) { + XCTAssertTrue(operation.isCancelled, "Operation should be cancelled") + exp.fulfill() + } + } + } + } + + // Wait for expectation to be fulfilled + waitForExpectations(timeout: 5, handler: nil) + } + func testOperationCanceledWithReason() { let exp = expectation(description: "Cancel operation with reason") let cancelReason = "PREARRANGED_REASON" @@ -734,65 +765,8 @@ class IntegrationTests: XCTestCase { } } } - // there are severalstejn backend calls, give it some time... - waitForExpectations(timeout: 40, handler: nil) - } - - // MARK: - Push - - func testRegisterPushLegacy() { - let expect = expectation(description: "Register push legacy") - push.registerDeviceTokenForPushNotifications(token: "testtoken".data(using: .utf8)!) { result in - if case .failure(let error) = result { - XCTFail("Failed to register push legacy: \(error.description)") - } - expect.fulfill() - } - XCTWaiter().wait(for: [expect], timeout: 20) - } - - func testRegisterPushApns() { - let expect = expectation(description: "Register push APNS") - push.register(to: .apns(token: "testtoken".data(using: .utf8)!)) { result in - if case .failure(let error) = result { - XCTFail("Failed to register APNS push: \(error)") - } - expect.fulfill() - } - XCTWaiter().wait(for: [expect], timeout: 20) - } - - func testRegisterPushApnsProduction() { - let expect = expectation(description: "Register push APNS") - push.register(to: .apns(token: "testtoken".data(using: .utf8)!, environment: .production)) { result in - if case .failure(let error) = result { - XCTFail("Failed to register APNS push: \(error)") - } - expect.fulfill() - } - XCTWaiter().wait(for: [expect], timeout: 20) - } - - func testRegisterPushApnsDevelopment() { - let expect = expectation(description: "Register push APNS") - push.register(to: .apns(token: "testtoken".data(using: .utf8)!, environment: .development)) { result in - if case .failure(let error) = result { - XCTFail("Failed to register APNS push: \(error)") - } - expect.fulfill() - } - XCTWaiter().wait(for: [expect], timeout: 20) - } - - func testRegisterPushFcm() { - let expect = expectation(description: "Register push APNS") - push.register(to: .fcm(token: "testtoken")) { result in - if case .failure(let error) = result { - XCTFail("Failed to register FCM push: \(error)") - } - expect.fulfill() - } - XCTWaiter().wait(for: [expect], timeout: 20) + // there are 3 backend calls, give it some time... + waitForExpectations(timeout: 20, handler: nil) } // MARK: - Inbox diff --git a/WultraMobileTokenSDKTests/NetworkingObjectsTests.swift b/WultraMobileTokenSDKTests/NetworkingObjectsTests.swift index e72a0ea..4c2d54d 100644 --- a/WultraMobileTokenSDKTests/NetworkingObjectsTests.swift +++ b/WultraMobileTokenSDKTests/NetworkingObjectsTests.swift @@ -34,66 +34,15 @@ class NetworkingObjectsTests: XCTestCase { super.tearDown() } - func testTokenRequestLegacy() { + func testTokenRequest() { let expectation = """ {"requestObject":{"platform":"ios","token":"5FBC85D026945C48A17FE1327C68C77F7793FEBFE23FF5850224BEE4215C5525"}} """ - let r = WMTPushEndpoints.RegisterDevice.EndpointType.RequestData(WMTPushRegistrationData(platform: .ios, token: "5FBC85D026945C48A17FE1327C68C77F7793FEBFE23FF5850224BEE4215C5525", environment: nil)) + let r = WMTPushEndpoints.RegisterDevice.EndpointType.RequestData(WMTPushRegistrationData(token: "5FBC85D026945C48A17FE1327C68C77F7793FEBFE23FF5850224BEE4215C5525")) r.testSerialization(expectation: expectation) } - func testTokenRequestLegacyWithEnvironment() { - let expectation = """ - {"requestObject":{"platform":"ios","token":"5FBC85D026945C48A17FE1327C68C77F7793FEBFE23FF5850224BEE4215C5525","environment":"development"}} - """ - let r = WMTPushEndpoints.RegisterDevice.EndpointType.RequestData(WMTPushRegistrationData(platform: .ios, token: "5FBC85D026945C48A17FE1327C68C77F7793FEBFE23FF5850224BEE4215C5525", environment: .development)) - - r.testSerialization(expectation: expectation) - } - - func testTokenRequestApnsDevelopment() { - let expectation = """ - {"requestObject":{"platform":"apns","token":"5FBC85D026945C48A17FE1327C68C77F7793FEBFE23FF5850224BEE4215C5525","environment":"development"}} - """ - let r = WMTPushEndpoints.RegisterDevice.EndpointType.RequestData(WMTPushRegistrationData(platform: .apns, token: "5FBC85D026945C48A17FE1327C68C77F7793FEBFE23FF5850224BEE4215C5525", environment: .development)) - - r.testSerialization(expectation: expectation) - } - - func testTokenRequestApnsProduction() { - let expectation = """ - {"requestObject":{"platform":"apns","token":"5FBC85D026945C48A17FE1327C68C77F7793FEBFE23FF5850224BEE4215C5525","environment":"production"}} - """ - let r = WMTPushEndpoints.RegisterDevice.EndpointType.RequestData(WMTPushRegistrationData(platform: .apns, token: "5FBC85D026945C48A17FE1327C68C77F7793FEBFE23FF5850224BEE4215C5525", environment: .production)) - - r.testSerialization(expectation: expectation) - } - - func testTokenRequestFcm() { - let expectation = """ - {"requestObject":{"platform":"fcm","token":"bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1"}} - """ - let r = WMTPushEndpoints.RegisterDevice.EndpointType.RequestData(WMTPushRegistrationData(platform: .fcm, token: "bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1", environment: nil)) - - r.testSerialization(expectation: expectation) - } - - func testApnsTokenSerialization() { - let apnsData = "testData".data(using: .utf8)! - let expectedApnsHex = "7465737444617461" - let apns = WMTPushPlatform.apns(token: apnsData, environment: .automatic) - // data should be transformed to hexformat - XCTAssertEqual(expectedApnsHex, apns.token) - } - - func testFcmTokenSerialization() { - let fcmData = "testToken" - let fcm = WMTPushPlatform.fcm(token: fcmData) - // FCM token should be the same - XCTAssertEqual(fcmData, fcm.token) - } - func testOperationsResponse() { let response = """ {"status":"OK","currentTimestamp":"2023-02-10T12:30:42+0000","responseObject":[{"id":"930febe7-f350-419a-8bc0-c8883e7f71e3","name":"authorize_payment","data":"A1*A100CZK*Q238400856/0300**D20170629*NUtility Bill Payment - 05/2017","status":"PENDING","operationCreated":"2018-08-08T12:30:42+0000","operationExpires":"2018-08-08T12:35:43+0000","allowedSignatureType":{"type":"2FA","variants":["possession_knowledge", "possession_biometry"]},"formData":{"title":"Potvrzení platby","message":"Dobrý den,prosíme o potvrzení následující platby:","attributes":[{"type":"AMOUNT","id":"operation.amount","label":"Částka","amount":965165234082.23,"currency":"CZK", "valueFormatted": "965165234082.23 CZK"},{"type":"KEY_VALUE","id":"operation.account","label":"Na účet","value":"238400856/0300"},{"type":"KEY_VALUE","id":"operation.dueDate","label":"Datum splatnosti","value":"29.6.2017"},{"type":"NOTE","id":"operation.note","label":"Poznámka","note":"Utility Bill Payment - 05/2017"},{"type":"PARTY_INFO","id":"operation.partyInfo","label":"Application","partyInfo":{"logoUrl":"http://whywander.com/wp-content/uploads/2017/05/prague_hero-100x100.jpg","name":"Tesco","description":"Objevte více příběhů psaných s chutí","websiteUrl":"https://itesco.cz/hello/vse-o-jidle/pribehy-psane-s-chuti/clanek/tomovy-burgery-pro-zapalene-fanousky/15012"}},{ "type": "AMOUNT_CONVERSION", "id": "operation.conversion", "label": "Conversion", "dynamic": true, "sourceAmount": 1.26, "sourceCurrency": "ETC", "sourceAmountFormatted": "1.26", "sourceCurrencyFormatted": "ETC", "sourceValueFormatted": "1.26 ETC", "targetAmount": 1710.98, "targetCurrency": "USD", "targetAmountFormatted": "1,710.98", "targetCurrencyFormatted": "USD", "targetValueFormatted": "1,710.98 USD"},{ "type": "IMAGE", "id": "operation.image", "label": "Image", "thumbnailUrl": "https://example.com/123_thumb.jpeg", "originalUrl": "https://example.com/123.jpeg" },{ "type": "IMAGE", "id": "operation.image", "label": "Image", "thumbnailUrl": "https://example.com/123_thumb.jpeg" }]}},{"id":"930febe7-f350-419a-8bc0-c8883e7f71e3","name":"authorize_payment","data":"A1*A100CZK*Q238400856/0300**D20170629*NUtility Bill Payment - 05/2017","status":"PENDING","operationCreated":"2018-08-08T12:30:42+0000","operationExpires":"2018-08-08T12:35:43+0000","allowedSignatureType":{"type":"1FA","variants":["possession_knowledge"]},"formData":{"title":"Potvrzení platby","message":"Dobrý den,prosíme o potvrzení následující platby:","attributes":[{"type":"AMOUNT","id":"operation.amount","label":"Částka","amount":100,"currency":"CZK"},{"type":"KEY_VALUE","id":"operation.account","label":"Na účet","value":"238400856/0300"},{"type":"KEY_VALUE","id":"operation.dueDate","label":"Datum splatnosti","value":"29.6.2017"},{"type":"NOTE","id":"operation.note","label":"Poznámka","note":"Utility Bill Payment - 05/2017"}]}}]} @@ -257,41 +206,41 @@ class NetworkingObjectsTests: XCTestCase { XCTAssertEqual("USD", conversionAttr.target.currencyFormatted) } - func testAmountAndConversionAttributesOnlyFormattedValues() { - let json = """ - {"status":"OK", "currentTimestamp":"2023-02-10T12:30:42+0000", "responseObject":[{"id":"930febe7-f350-419a-8bc0-c8883e7f71e3", "name":"authorize_payment", "status":"PENDING", "data":"A1*A100CZK*Q238400856/0300**D20170629*NUtility Bill Payment - 05/2017", "operationCreated":"2018-08-08T12:30:42+0000", "operationExpires":"2018-08-08T12:35:43+0000", "allowedSignatureType": {"type":"2FA", "variants": ["possession_knowledge", "possession_biometry"]}, "formData": {"title":"Potvrzení platby", "message":"Dobrý den,prosíme o potvrzení následující platby:", "attributes": [{"type":"AMOUNT", "id":"operation.amount", "label":"Částka", "amountFormatted":"965165234082.23", "currencyFormatted":"CZK"}, { "type": "AMOUNT_CONVERSION", "id": "operation.conversion", "label": "Conversion", "dynamic": true, "sourceAmountFormatted": "1.26", "sourceCurrencyFormatted": "ETC", "targetAmountFormatted": "1710.98", "targetCurrencyFormatted": "USD"}]}}]} - """.trimmingCharacters(in: .whitespacesAndNewlines) + func testAmountAndConversionAttributesOnlyFormattedValues() { + let json = """ + {"status":"OK", "currentTimestamp":"2023-02-10T12:30:42+0000", "responseObject":[{"id":"930febe7-f350-419a-8bc0-c8883e7f71e3", "name":"authorize_payment", "status":"PENDING", "data":"A1*A100CZK*Q238400856/0300**D20170629*NUtility Bill Payment - 05/2017", "operationCreated":"2018-08-08T12:30:42+0000", "operationExpires":"2018-08-08T12:35:43+0000", "allowedSignatureType": {"type":"2FA", "variants": ["possession_knowledge", "possession_biometry"]}, "formData": {"title":"Potvrzení platby", "message":"Dobrý den,prosíme o potvrzení následující platby:", "attributes": [{"type":"AMOUNT", "id":"operation.amount", "label":"Částka", "amountFormatted":"965165234082.23", "currencyFormatted":"CZK"}, { "type": "AMOUNT_CONVERSION", "id": "operation.conversion", "label": "Conversion", "dynamic": true, "sourceAmountFormatted": "1.26", "sourceCurrencyFormatted": "ETC", "targetAmountFormatted": "1710.98", "targetCurrencyFormatted": "USD"}]}}]} + """.trimmingCharacters(in: .whitespacesAndNewlines) - guard let result = try? jsonDecoder.decode(WPNResponseArray.self, from: json.data(using: .utf8)!) else { - XCTFail("Failed to parse JSON data") - return - } + guard let result = try? jsonDecoder.decode(WPNResponseArray.self, from: json.data(using: .utf8)!) else { + XCTFail("Failed to parse JSON data") + return + } - guard let amountAttr = result.responseObject?[0].formData.attributes[0] as? WMTOperationAttributeAmount else { - XCTFail("amount attribute not recognized") - return - } + guard let amountAttr = result.responseObject?[0].formData.attributes[0] as? WMTOperationAttributeAmount else { + XCTFail("amount attribute not recognized") + return + } - XCTAssertNil(amountAttr.amount) - XCTAssertNil(amountAttr.currency) - XCTAssertEqual("965165234082.23", amountAttr.amountFormatted) - XCTAssertEqual("CZK", amountAttr.currencyFormatted) + XCTAssertNil(amountAttr.amount) + XCTAssertNil(amountAttr.currency) + XCTAssertEqual("965165234082.23", amountAttr.amountFormatted) + XCTAssertEqual("CZK", amountAttr.currencyFormatted) - guard let conversionAttr = result.responseObject?[0].formData.attributes[1] as? WMTOperationAttributeAmountConversion else { - XCTFail("conversion attribute not recognized") - return - } + guard let conversionAttr = result.responseObject?[0].formData.attributes[1] as? WMTOperationAttributeAmountConversion else { + XCTFail("conversion attribute not recognized") + return + } - XCTAssertNil(conversionAttr.source.amount) - XCTAssertNil(conversionAttr.source.currency) - XCTAssertNil(conversionAttr.target.amount) - XCTAssertNil(conversionAttr.target.currency) + XCTAssertNil(conversionAttr.source.amount) + XCTAssertNil(conversionAttr.source.currency) + XCTAssertNil(conversionAttr.target.amount) + XCTAssertNil(conversionAttr.target.currency) - XCTAssertEqual("1.26", conversionAttr.source.amountFormatted) - XCTAssertEqual("ETC", conversionAttr.source.currencyFormatted) - XCTAssertEqual("1710.98", conversionAttr.target.amountFormatted) - XCTAssertEqual("USD", conversionAttr.target.currencyFormatted) - } + XCTAssertEqual("1.26", conversionAttr.source.amountFormatted) + XCTAssertEqual("ETC", conversionAttr.source.currencyFormatted) + XCTAssertEqual("1710.98", conversionAttr.target.amountFormatted) + XCTAssertEqual("USD", conversionAttr.target.currencyFormatted) + } func testErrorResponse() { diff --git a/WultraMobileTokenSDKTests/ProvisioningUtilsTests.swift b/WultraMobileTokenSDKTests/ProvisioningUtilsTests.swift deleted file mode 100644 index 1e7367d..0000000 --- a/WultraMobileTokenSDKTests/ProvisioningUtilsTests.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// Copyright 2020 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 XCTest -@testable import WultraMobileTokenSDK - -class ProvisioningUtilsTests: XCTestCase { - - // provisioning profile parsing - - func testParseProvisioningProfile() { - guard let data = Data(base64Encoded: base64ProductionProfile) else { - XCTFail("Could not decode base64 profile") - return - } - let profile = WMTProvisioningUtils.getProvisioningProfileFromData(data) - XCTAssertEqual(profile?.entitlements.apsEnvironment, .production) - } - - // plist parsing - - func testParseDevelopmentAPNS() { - let plist = getPlist("development") - let profile = WMTProvisioningUtils.parseProvisioningProfilePlist(plist) - XCTAssertEqual(profile?.entitlements.apsEnvironment, .development) - } - - func testParseProductionAPNS() { - let plist = getPlist("production") - let profile = WMTProvisioningUtils.parseProvisioningProfilePlist(plist) - XCTAssertEqual(profile?.entitlements.apsEnvironment, .production) - } - - func testParseMissingAPNS() { - let plist = getPlist(nil) - let profile = WMTProvisioningUtils.parseProvisioningProfilePlist(plist) - XCTAssertEqual(profile?.entitlements.apsEnvironment, nil) - } - - func testParseUnknownAPNS() { - let plist = getPlist("integration") // unknown value - let profile = WMTProvisioningUtils.parseProvisioningProfilePlist(plist) - XCTAssertEqual(profile?.entitlements.apsEnvironment, nil) - } - - private func getPlist(_ apnsEnvironment: String?) -> Data { - let entry = if let apnsEnvironment { - "aps-environment\(apnsEnvironment)" - } else { - "" - } - return " AppIDName for testing purposes ApplicationIdentifierPrefix ASDASDASD CreationDate 2024-01-08T10:59:17Z Platform iOS xrOS visionOS IsXcodeManaged DeveloperCertificates pqi3u4hrjkanfkjanldfjkansd;fiohLmFwcC5Nb2JpbGVUb2tlbi5kZXYwggEIDBVEZXZlbG9wZXJDZXJ0aWZpY2F0ZXMwge4EILJ8BwOI38tdzUcaq6hIvRRO6D2QmN9HDEf7QPB+6axsBCBvpLxOYiaJykpTpjoFwyvfZclkQGG4S2Cw0HC8qrenSwQgkdZR2h310zeFWcJiJua9POg+E1qI0DXyAH5rGHXbnQsEIMr0/eY+Qqyujx1sNvG12cizX5KaudM3CjtzvsVqIKnkBCDccTpELoQR0s/FEqUpCem1yCEqtgKE8kD3kOAdggSYJQQgxcz/0oI5Y8l0uI7f5zgaT1wi3WomccHWmtoOq3HPyjIEIElhcDeOADtptU1rQvX21BVbyIKYOwwEGDa1bJ+QeATEMIIBiwwMRW50aXRsZW1lbnRzcIIBeQIBAbCCAXIwQwwWYXBwbGljYXRpb24taWRlbnRpZmllcgwpS1RUOUc4NTlNUi5jb20ud3VsdHJhLmFwcC5Nb2JpbGVUb2tlbi5kZXYwHgwPYXBzLWVudmlyb25tZW50DAtkZXZlbG9wbWVudDAxDCNjb20uYXBwbGUuZGV2ZWxvcGVyLnRlYW0taWRlbnRpZmllcgwKS1RUOUc4NTlNUjCBhwwlY29tLmFwcGxlLnNlY3VyaXR5LmFwcGxpY2F0aW9uLWdyb3VwczBeDBpncm91cC5jb20ud3VsdHJhLnRlc3RHcm91cAxAZ3JvdXAuQ1ktMEZDMzEwNUMtRUY2RC0xMUU5LTgyM0ItMzMxMzc0MDhCQzk2LmNvbS5jeWRpYS5FeHRlbmRlcjATDA5nZXQtdGFzay1hbGxvdwEB/zA5DBZrZXljaGFpbi1hY2Nlc3MtZ3JvdXBzMB8MDEtUVDlHODU5TVIuKgwPY29tLmFwcGxlLnRva2VuMIIGpQwSUHJvdmlzaW9uZWREZXZpY2VzMIIGjQwZMDAwMDgxMDEtMDAwNTI4RDkzQTEyMDAxRQwZMDAwMjelknfkjaerl;f DER-Encoded-Profile UftjJiXcnphFtPME8RWgD9WFgMpfUPLE0HRxN12peXl28xXO0rVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFLuw3qFYM4iapIqZ3r6966/ayySrMEYGCCsGAQUFBwEBBDowODA2BggrBgEFBQcwAYYqaHR0cDovL29jc3AuYXBwbGUuY29tL29jc3AwMy1hcHBsZXJvb3RjYWczMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly9jcmwuYXBwbGUuY29tL2FwcGxlcm9vdGNhZzMuY3JsMB0GA1UdDgQWBBR6R7o4ihUkSCJGzb6PGiR7NAMqaTAOBgNVHQ8BAf8EBAMCQcm9kdWN0cyBhbmQvb3IgQXBwbGUgcHJvY2Vzc2VzLjAdBgNVHQ4EFgQUDgWBWc9LzVC4LP5b4EGBqz8zz+8wDgYDVR0PAQH/BAQDAgeAMA8GCSqGSIb3Y2QMEwQCBQAwCgYIKoZIzj0EAwIDRwAwRAIgSl19xJZLrQqOZYa8t493EpwrI7/L2J8LhbwYpW7gO80CIGiD58K1x1h1Cp43EHeyVRIYiVBThZVwbxWxLF7W8pwuMYIB1zCCAdMCAQEwfjByMSYwJAYDVQQDDB1BcHBsZSBTeXN0ZW0gSW50ZWdyYXRpb24gQ0EgNDEmMCQGA1UECwwdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxEzARBgNVBAoMCkFwcGxlIEluYy4xCzAJBgNVBAYTAlVTAggQZ+4OarG1YjANBglghkgBZQMEAgEFAKCB6TAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0yNDAxMDgxMDU5MTdaMCoGCSqGSIb3DQEJNDEdMBswDQYJYIZIAWUDBAIBBQChCgYIKoZIzj0EAwIwLwYJKoZIhvcNAQkEMSIEIMRi+YCgI1wI14ztb05ZvyGPMqw3KEKsLMcwLVtJ6yC7MFIGCSqGSIb3DQEJDzFFMEMwCgYIKoZIhvcNAwcwDgYIKoZIhvcNAwICAgCAMA0GCCqGSIb3DQMCAgFAMAcGBSsOAwIHMA0GCCqGSIb3DQMCAgEoMAoGCCqGSM49BAMCBEcwRQIhAMc5IyUcaFaKFEyjmo7hMiMGYYg6m+iJnLY6M4vc88yhAiB94O5nW+8T9zaGzCOB2Ey8VWEKTioOPyOSc8GZVntlHQ== Entitlements \(entry) com.apple.security.application-groups group.com.wultra.testGroup application-identifier ASDASDASD.com.wultra.test keychain-access-groups ASDASDASD.* com.apple.token get-task-allow com.apple.developer.team-identifier ASDASDASD ExpirationDate 2025-01-07T10:59:17Z Name iOS Team Provisioning Profile: com.wultra.test ProvisionedDevices 00008101-000528D93A12001E 00008101-001C34EC2250801E 00008030-000975CC0AEA802E 00008101-001269DE0A50001E TeamIdentifier ASDASDASD TeamName Wultra TimeToLive 365 UUID 7749747f-897e-45f3-b78e-d327a1c9f38a Version 1 ".data(using: .utf8)! - } - - // real base64 encoded embedded.mobileprovisioning from the testflight app with a profuction apns - private let base64ProductionProfile = "MIIxVAYJKoZIhvcNAQcCoIIxRTCCMUECAQExCzAJBgUrDgMCGgUAMIIhYQYJKoZIhvcNAQcBoIIhUgSCIU48P3htbCB2ZXJzaW9uPSIxLjAiIGVuY29kaW5nPSJVVEYtOCI/Pgo8IURPQ1RZUEUgcGxpc3QgUFVCTElDICItLy9BcHBsZS8vRFREIFBMSVNUIDEuMC8vRU4iICJodHRwOi8vd3d3LmFwcGxlLmNvbS9EVERzL1Byb3BlcnR5TGlzdC0xLjAuZHRkIj4KPHBsaXN0IHZlcnNpb249IjEuMCI+CjxkaWN0PgoJPGtleT5BcHBJRE5hbWU8L2tleT4KCTxzdHJpbmc+WEMgY29tIHd1bHRyYSBhcHAgTW9iaWxlVG9rZW48L3N0cmluZz4KCTxrZXk+QXBwbGljYXRpb25JZGVudGlmaWVyUHJlZml4PC9rZXk+Cgk8YXJyYXk+Cgk8c3RyaW5nPktUVDlHODU5TVI8L3N0cmluZz4KCTwvYXJyYXk+Cgk8a2V5PkNyZWF0aW9uRGF0ZTwva2V5PgoJPGRhdGU+MjAyNC0xMC0yNFQxNzoyNTo0Nlo8L2RhdGU+Cgk8a2V5PlBsYXRmb3JtPC9rZXk+Cgk8YXJyYXk+CgkJPHN0cmluZz5pT1M8L3N0cmluZz4KCQk8c3RyaW5nPnhyT1M8L3N0cmluZz4KCQk8c3RyaW5nPnZpc2lvbk9TPC9zdHJpbmc+Cgk8L2FycmF5PgoJPGtleT5Jc1hjb2RlTWFuYWdlZDwva2V5PgoJPGZhbHNlLz4KCTxrZXk+RGV2ZWxvcGVyQ2VydGlmaWNhdGVzPC9rZXk+Cgk8YXJyYXk+CgkJPGRhdGE+TUlJRnlUQ0NCTEdnQXdJQkFnSVFCaDh3VkxVakRyQVIrcWJHcGJpWENUQU5CZ2txaGtpRzl3MEJBUXNGQURCMU1VUXdRZ1lEVlFRREREdEJjSEJzWlNCWGIzSnNaSGRwWkdVZ1JHVjJaV3h2Y0dWeUlGSmxiR0YwYVc5dWN5QkRaWEowYVdacFkyRjBhVzl1SUVGMWRHaHZjbWwwZVRFTE1Ba0dBMVVFQ3d3Q1J6TXhFekFSQmdOVkJBb01Da0Z3Y0d4bElFbHVZeTR4Q3pBSkJnTlZCQVlUQWxWVE1CNFhEVEkwTVRBeU5ERTNNVEF6TUZvWERUSTFNVEF5TkRFM01UQXlPVm93Z1k4eEdqQVlCZ29Ka2lhSmsvSXNaQUVCREFwTFZGUTVSemcxT1UxU01UY3dOUVlEVlFRRERDNUJjSEJzWlNCRWFYTjBjbWxpZFhScGIyNDZJRmQxYkhSeVlTQnpMbkl1Ynk0Z0tFdFVWRGxIT0RVNVRWSXBNUk13RVFZRFZRUUxEQXBMVkZRNVJ6ZzFPVTFTTVJZd0ZBWURWUVFLREExWGRXeDBjbUVnY3k1eUxtOHVNUXN3Q1FZRFZRUUdFd0pEV2pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTzBxY21uRGt6R0R4dExOUmREcWJmNkVnVjF5SWFXZDlxRWtETWhuSkFPMDlzUGh5eVM2dHNnaVB6ZExocUJGZmxBcXNad2Fnd0p5M1piMkFHbEpVZmM0b1VrV0MrLzQyQmlKM3QybzJ2L0ttcnBVZlkxcU9sZjdlTFZ5cVdYRVVJWGZtKzI4dkMyR2ZJeWZKSzdsWU1kMlpxY1pScnZqb1h1TzVKS3NmckNUTzZvQWZHSkJINy9pbDFXcitHZi9vbXkzOTRKdzIzb1g0dTNqOExPR254akRpemlDTHArMTlNNWhIeEVPWjJBZzFqbDR2SVFJZGRIbzAxaUhpWkJUNjNENjFJcmNNejViM0FDRjVrR2RzbS9HU2VMdzkvSXI5YlF0cUQrUkFHTFo3KzN5ODN0OERSRE1rQXhwWUc1VE9jRlpJZWhObVB5VkQxQlF0L2lUTEtzQ0F3RUFBYU9DQWpnd2dnSTBNQXdHQTFVZEV3RUIvd1FDTUFBd0h3WURWUjBqQkJnd0ZvQVVDZjdBRlpENXIyUUtraEs1SmloakRKZnNwN0l3Y0FZSUt3WUJCUVVIQVFFRVpEQmlNQzBHQ0NzR0FRVUZCekFDaGlGb2RIUndPaTh2WTJWeWRITXVZWEJ3YkdVdVkyOXRMM2QzWkhKbk15NWtaWEl3TVFZSUt3WUJCUVVITUFHR0pXaDBkSEE2THk5dlkzTndMbUZ3Y0d4bExtTnZiUzl2WTNOd01ETXRkM2RrY21jek1EVXdnZ0VlQmdOVkhTQUVnZ0VWTUlJQkVUQ0NBUTBHQ1NxR1NJYjNZMlFGQVRDQi96Q0J3d1lJS3dZQkJRVUhBZ0l3Z2JZTWdiTlNaV3hwWVc1alpTQnZiaUIwYUdseklHTmxjblJwWm1sallYUmxJR0o1SUdGdWVTQndZWEowZVNCaGMzTjFiV1Z6SUdGalkyVndkR0Z1WTJVZ2IyWWdkR2hsSUhSb1pXNGdZWEJ3YkdsallXSnNaU0J6ZEdGdVpHRnlaQ0IwWlhKdGN5QmhibVFnWTI5dVpHbDBhVzl1Y3lCdlppQjFjMlVzSUdObGNuUnBabWxqWVhSbElIQnZiR2xqZVNCaGJtUWdZMlZ5ZEdsbWFXTmhkR2x2YmlCd2NtRmpkR2xqWlNCemRHRjBaVzFsYm5SekxqQTNCZ2dyQmdFRkJRY0NBUllyYUhSMGNITTZMeTkzZDNjdVlYQndiR1V1WTI5dEwyTmxjblJwWm1sallYUmxZWFYwYUc5eWFYUjVMekFXQmdOVkhTVUJBZjhFRERBS0JnZ3JCZ0VGQlFjREF6QWRCZ05WSFE0RUZnUVV5OC9ZOVZLbVpGNjBtTm9hK2NvQ3dDM3JtQjh3RGdZRFZSMFBBUUgvQkFRREFnZUFNQk1HQ2lxR1NJYjNZMlFHQVFjQkFmOEVBZ1VBTUJNR0NpcUdTSWIzWTJRR0FRUUJBZjhFQWdVQU1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQVVaZmthZlFnOFFrMXpYd3NaUk9sWEVidG5rTXkxWUlHUlRxd3NSd3dlVUc0akNKeXRXQ2lWQTRoOG8wWHIzU3lsQjJBOHFMNFZxVHdSckg4NXlwcEJmN2xHRFEwc0E1V0YwTnZxbndVSTlvd1ZQdjg1WkVORmo1OVgyUEJjVDE0S0VIRjNOY1ZHdWl3SmIvL24zNnk0bFVxNFRoS0pBZmQvYVpyUlVuRjlNSTAvWm0vSVVqbTBaNUpxR1RHdUh6bXVtYVk5bFQ2enVlYkZycVVNbE1QQWd2eW9MZjFHaGlOZXA4RU1VK045TGxDcE1KZ3FTUm43eSs2M1VOOFk4OTdqRzVHcDdmUUw3V3BUdjAyWVg5dFp5WFFwSi9rUEpyNmZTYkYyUU10NFl0ZldWQ3NkV3lrU0hRZUxYU21lNTMrOWt0RWZVZHpJUlRmK2hBc3ZCVzFxPC9kYXRhPgoJPC9hcnJheT4KCgk8a2V5PkRFUi1FbmNvZGVkLVByb2ZpbGU8L2tleT4KCTxkYXRhPk1JSU4wd1lKS29aSWh2Y05BUWNDb0lJTnhEQ0NEY0FDQVFFeER6QU5CZ2xnaGtnQlpRTUVBZ0VGQURDQ0E0MEdDU3FHU0liM0RRRUhBYUNDQTM0RWdnTjZNWUlEZGpBTURBZFdaWEp6YVc5dUFnRUJNQkFNQ2xScGJXVlViMHhwZG1VQ0FnRnNNQk1NRGtseldHTnZaR1ZOWVc1aFoyVmtBUUVBTUJjTUJFNWhiV1VNRDFkMWJIUnlZU0JMWlhrZ1VISnZaREFaREFoVVpXRnRUbUZ0WlF3TlYzVnNkSEpoSUhNdWNpNXZMakFkREF4RGNtVmhkR2x2YmtSaGRHVVhEVEkwTVRBeU5ERTNNalUwTmxvd0hnd09WR1ZoYlVsa1pXNTBhV1pwWlhJd0RBd0tTMVJVT1VjNE5UbE5VakFmREE1RmVIQnBjbUYwYVc5dVJHRjBaUmNOTWpVeE1ESTBNVGN4TURJNVdqQWdEQmRRY205bWFXeGxSR2x6ZEhKcFluVjBhVzl1Vkhsd1pRd0ZVMVJQVWtVd0lRd0lVR3hoZEdadmNtMHdGUXdEYVU5VERBUjRjazlUREFoMmFYTnBiMjVQVXpBcURBbEJjSEJKUkU1aGJXVU1IVmhESUdOdmJTQjNkV3gwY21FZ1lYQndJRTF2WW1sc1pWUnZhMlZ1TUNzTUcwRndjR3hwWTJGMGFXOXVTV1JsYm5ScFptbGxjbEJ5WldacGVEQU1EQXBMVkZRNVJ6ZzFPVTFTTUN3TUJGVlZTVVFNSkRCbU5UQTVOakUyTFRNeE16VXRORE01TUMwNFptUTBMVE13TmpNek1URXpaR1k0TWpBN0RCVkVaWFpsYkc5d1pYSkRaWEowYVdacFkyRjBaWE13SWdRZ0R4bzdseTBUdUQ1U3FKalpXbHNLRTFjVkRzR0kwdGR2VERMT2xkWWRsQ3N3Z2dHZ0RBeEZiblJwZEd4bGJXVnVkSE53Z2dHT0FnRUJzSUlCaHpBL0RCWmhjSEJzYVdOaGRHbHZiaTFwWkdWdWRHbG1hV1Z5RENWTFZGUTVSemcxT1UxU0xtTnZiUzUzZFd4MGNtRXVZWEJ3TGsxdlltbHNaVlJ2YTJWdU1CME1EMkZ3Y3kxbGJuWnBjbTl1YldWdWRBd0tjSEp2WkhWamRHbHZiakFZREJOaVpYUmhMWEpsY0c5eWRITXRZV04wYVhabEFRSC9NREVNSTJOdmJTNWhjSEJzWlM1a1pYWmxiRzl3WlhJdWRHVmhiUzFwWkdWdWRHbG1hV1Z5REFwTFZGUTVSemcxT1UxU01JR0hEQ1ZqYjIwdVlYQndiR1V1YzJWamRYSnBkSGt1WVhCd2JHbGpZWFJwYjI0dFozSnZkWEJ6TUY0TUdtZHliM1Z3TG1OdmJTNTNkV3gwY21FdWRHVnpkRWR5YjNWd0RFQm5jbTkxY0M1RFdTMHdSa016TVRBMVF5MUZSalpFTFRFeFJUa3RPREl6UWkwek16RXpOelF3T0VKRE9UWXVZMjl0TG1ONVpHbGhMa1Y0ZEdWdVpHVnlNQk1NRG1kbGRDMTBZWE5yTFdGc2JHOTNBUUVBTURrTUZtdGxlV05vWVdsdUxXRmpZMlZ6Y3kxbmNtOTFjSE13SHd3TVMxUlVPVWM0TlRsTlVpNHFEQTlqYjIwdVlYQndiR1V1ZEc5clpXNmdnZ2c4TUlJQ1F6Q0NBY21nQXdJQkFnSUlMY1g4aU5MRlM1VXdDZ1lJS29aSXpqMEVBd013WnpFYk1Ca0dBMVVFQXd3U1FYQndiR1VnVW05dmRDQkRRU0F0SUVjek1TWXdKQVlEVlFRTERCMUJjSEJzWlNCRFpYSjBhV1pwWTJGMGFXOXVJRUYxZEdodmNtbDBlVEVUTUJFR0ExVUVDZ3dLUVhCd2JHVWdTVzVqTGpFTE1Ba0dBMVVFQmhNQ1ZWTXdIaGNOTVRRd05ETXdNVGd4T1RBMldoY05Nemt3TkRNd01UZ3hPVEEyV2pCbk1Sc3dHUVlEVlFRRERCSkJjSEJzWlNCU2IyOTBJRU5CSUMwZ1J6TXhKakFrQmdOVkJBc01IVUZ3Y0d4bElFTmxjblJwWm1sallYUnBiMjRnUVhWMGFHOXlhWFI1TVJNd0VRWURWUVFLREFwQmNIQnNaU0JKYm1NdU1Rc3dDUVlEVlFRR0V3SlZVekIyTUJBR0J5cUdTTTQ5QWdFR0JTdUJCQUFpQTJJQUJKanBMejFBY3FUdGt5SnlnUk1jM1JDVjhjV2pUbkhjRkJiWkR1V21CU3AzWkh0ZlRqalR1eHhFdFgvMUg3WXlZbDNKNllSYlR6QlBFVm9BL1ZoWURLWDFEeXhOQjBjVGRkcVhsNWR2TVZ6dEs1MTdJRHZZdVZUWlhwbWtPbEVLTWFOQ01FQXdIUVlEVlIwT0JCWUVGTHV3M3FGWU00aWFwSXFaM3I2OTY2L2F5eVNyTUE4R0ExVWRFd0VCL3dRRk1BTUJBZjh3RGdZRFZSMFBBUUgvQkFRREFnRUdNQW9HQ0NxR1NNNDlCQU1EQTJnQU1HVUNNUUNENmNIRUZsNGFYVFFZMmUzdjlHd09BRVpMdU4reVJoSEZELzNtZW95aHBtdk93Z1BVblBXVHhuUzRhdCtxSXhVQ01HMW1paERLMUEzVVQ4Mk5RejYwaW1PbE0yN2piZG9YdDJRZnlGTW0rWWhpZERrTEYxdkxVYWdNNkJnRDU2S3lLRENDQXVZd2dnSnRvQU1DQVFJQ0NETU43dmkvVEdndU1Bb0dDQ3FHU000OUJBTURNR2N4R3pBWkJnTlZCQU1NRWtGd2NHeGxJRkp2YjNRZ1EwRWdMU0JITXpFbU1DUUdBMVVFQ3d3ZFFYQndiR1VnUTJWeWRHbG1hV05oZEdsdmJpQkJkWFJvYjNKcGRIa3hFekFSQmdOVkJBb01Da0Z3Y0d4bElFbHVZeTR4Q3pBSkJnTlZCQVlUQWxWVE1CNFhEVEUzTURJeU1qSXlNak15TWxvWERUTXlNREl4T0RBd01EQXdNRm93Y2pFbU1DUUdBMVVFQXd3ZFFYQndiR1VnVTNsemRHVnRJRWx1ZEdWbmNtRjBhVzl1SUVOQklEUXhKakFrQmdOVkJBc01IVUZ3Y0d4bElFTmxjblJwWm1sallYUnBiMjRnUVhWMGFHOXlhWFI1TVJNd0VRWURWUVFLREFwQmNIQnNaU0JKYm1NdU1Rc3dDUVlEVlFRR0V3SlZVekJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCQVpycEZadmZaOG4wYzQyanBJYlZzMVVObVJLeVpSb21mckpJSDdpOVZnUDNPSnE2eGxITHk3dk82UUJ0QUVUUkh4YUpxMmduQ2tsaXVYbUJtOVBmRnFqZ2Zjd2dmUXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QWZCZ05WSFNNRUdEQVdnQlM3c042aFdET0ltcVNLbWQ2K3ZldXYyc3NrcXpCR0JnZ3JCZ0VGQlFjQkFRUTZNRGd3TmdZSUt3WUJCUVVITUFHR0ttaDBkSEE2THk5dlkzTndMbUZ3Y0d4bExtTnZiUzl2WTNOd01ETXRZWEJ3YkdWeWIyOTBZMkZuTXpBM0JnTlZIUjhFTURBdU1DeWdLcUFvaGlab2RIUndPaTh2WTNKc0xtRndjR3hsTG1OdmJTOWhjSEJzWlhKdmIzUmpZV2N6TG1OeWJEQWRCZ05WSFE0RUZnUVVla2U2T0lvVkpFZ2lSczIranhva2V6UURLbWt3RGdZRFZSMFBBUUgvQkFRREFnRUdNQkFHQ2lxR1NJYjNZMlFHQWhFRUFnVUFNQW9HQ0NxR1NNNDlCQU1EQTJjQU1HUUNNQlVNcVk3R3I1WnBhNmVmM1Z6VUExbHNybExVWU1hTGR1QzN4YUx4Q1h6Z211TnJzZU44TWNRbmVxZU9pZjJyZHdJd1lUTWc4U24vK1ljeXJpbklaRDEyZTFHazBnSXZkcjVnSXBIeDFUcDEzTFRpeGlxVy9zWUozRXBQMVNUdy9NcXlNSUlEQnpDQ0FxMmdBd0lCQWdJSVhLMG1KQk1ZQmI4d0NnWUlLb1pJemowRUF3SXdjakVtTUNRR0ExVUVBd3dkUVhCd2JHVWdVM2x6ZEdWdElFbHVkR1ZuY21GMGFXOXVJRU5CSURReEpqQWtCZ05WQkFzTUhVRndjR3hsSUVObGNuUnBabWxqWVhScGIyNGdRWFYwYUc5eWFYUjVNUk13RVFZRFZRUUtEQXBCY0hCc1pTQkpibU11TVFzd0NRWURWUVFHRXdKVlV6QWVGdzB5TkRBeE1qa3hOalEzTURSYUZ3MHlPREF5TWpjeE5qUTNNRE5hTUU0eEtqQW9CZ05WQkFNTUlWZFhSRklnVUhKdmRtbHphVzl1YVc1bklGQnliMlpwYkdVZ1UybG5ibWx1WnpFVE1CRUdBMVVFQ2d3S1FYQndiR1VnU1c1akxqRUxNQWtHQTFVRUJoTUNWVk13V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVRFRGhQRHo2N3hFQzkrOVZUYlZHdUVCeC9qNWljUXFIVldpbFQ1QWE1L0k4VVNnSEFMNzJ1TUw3T3BEbjBCV2d0S05ZQWFHQWhibG5zcVpRZno0dDVFbzRJQlR6Q0NBVXN3REFZRFZSMFRBUUgvQkFJd0FEQWZCZ05WSFNNRUdEQVdnQlI2UjdvNGloVWtTQ0pHemI2UEdpUjdOQU1xYVRCQkJnZ3JCZ0VGQlFjQkFRUTFNRE13TVFZSUt3WUJCUVVITUFHR0pXaDBkSEE2THk5dlkzTndMbUZ3Y0d4bExtTnZiUzl2WTNOd01ETXRZWE5wWTJFME1ETXdnWllHQTFVZElBU0JqakNCaXpDQmlBWUpLb1pJaHZkalpBVUJNSHN3ZVFZSUt3WUJCUVVIQWdJd2JReHJWR2hwY3lCalpYSjBhV1pwWTJGMFpTQnBjeUIwYnlCaVpTQjFjMlZrSUdWNFkyeDFjMmwyWld4NUlHWnZjaUJtZFc1amRHbHZibk1nYVc1MFpYSnVZV3dnZEc4Z1FYQndiR1VnVUhKdlpIVmpkSE1nWVc1a0wyOXlJRUZ3Y0d4bElIQnliMk5sYzNObGN5NHdIUVlEVlIwT0JCWUVGR3YvWFFPVHV2SEY0cmowQ2piTTFFQ0w4WHdlTUE0R0ExVWREd0VCL3dRRUF3SUhnREFQQmdrcWhraUc5Mk5rREJNRUFnVUFNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJSGV6YjVqVmx3Uk1kV2N6TzlKQUoxSXJ0U041ZlRIN2ZIMVhXV3VlT2RMWkFpRUE3eFI4aExtQ1JTVnJIaUZoZCtkMnpEd3dJNWYyb2djRFFEck9FdkdkRFMweGdnSFhNSUlCMHdJQkFUQitNSEl4SmpBa0JnTlZCQU1NSFVGd2NHeGxJRk41YzNSbGJTQkpiblJsWjNKaGRHbHZiaUJEUVNBME1TWXdKQVlEVlFRTERCMUJjSEJzWlNCRFpYSjBhV1pwWTJGMGFXOXVJRUYxZEdodmNtbDBlVEVUTUJFR0ExVUVDZ3dLUVhCd2JHVWdTVzVqTGpFTE1Ba0dBMVVFQmhNQ1ZWTUNDRnl0SmlRVEdBVy9NQTBHQ1dDR1NBRmxBd1FDQVFVQW9JSHBNQmdHQ1NxR1NJYjNEUUVKQXpFTEJna3Foa2lHOXcwQkJ3RXdIQVlKS29aSWh2Y05BUWtGTVE4WERUSTBNVEF5TkRFM01qVTBObG93S2dZSktvWklodmNOQVFrME1SMHdHekFOQmdsZ2hrZ0JaUU1FQWdFRkFLRUtCZ2dxaGtqT1BRUURBakF2QmdrcWhraUc5dzBCQ1FReElnUWcxeC96UHA4OCt3dnpKY3dQYUc2diszR3ltcDlkMXdPUUpDSjBUL3VrWDFNd1VnWUpLb1pJaHZjTkFRa1BNVVV3UXpBS0JnZ3Foa2lHOXcwREJ6QU9CZ2dxaGtpRzl3MERBZ0lDQUlBd0RRWUlLb1pJaHZjTkF3SUNBVUF3QndZRkt3NERBZ2N3RFFZSUtvWklodmNOQXdJQ0FTZ3dDZ1lJS29aSXpqMEVBd0lFUnpCRkFpRUFwZlp5andXUENyUXB5NENQYU8rWXh0N0NPamJnNzZrMDNlVXArbEpVWTM4Q0lIbVBXd1dhVGxCbS8ramk5YjZDWXZvZTBabkdNY2EwdHNYZG9TYksyM1kvPC9kYXRhPgoJCQkJCQkJCQkJCQkKCTxrZXk+RW50aXRsZW1lbnRzPC9rZXk+Cgk8ZGljdD4KCQk8a2V5PmJldGEtcmVwb3J0cy1hY3RpdmU8L2tleT4KCQk8dHJ1ZS8+CgkJCQkKCQkJCTxrZXk+YXBzLWVudmlyb25tZW50PC9rZXk+CgkJPHN0cmluZz5wcm9kdWN0aW9uPC9zdHJpbmc+CgkJCQkKCQkJCTxrZXk+Y29tLmFwcGxlLnNlY3VyaXR5LmFwcGxpY2F0aW9uLWdyb3Vwczwva2V5PgoJCTxhcnJheT4KCQkJCTxzdHJpbmc+Z3JvdXAuY29tLnd1bHRyYS50ZXN0R3JvdXA8L3N0cmluZz4KCQkJCTxzdHJpbmc+Z3JvdXAuQ1ktMEZDMzEwNUMtRUY2RC0xMUU5LTgyM0ItMzMxMzc0MDhCQzk2LmNvbS5jeWRpYS5FeHRlbmRlcjwvc3RyaW5nPgoJCTwvYXJyYXk+CgkJCQkKCQkJCTxrZXk+YXBwbGljYXRpb24taWRlbnRpZmllcjwva2V5PgoJCTxzdHJpbmc+S1RUOUc4NTlNUi5jb20ud3VsdHJhLmFwcC5Nb2JpbGVUb2tlbjwvc3RyaW5nPgoJCQkJCgkJCQk8a2V5PmtleWNoYWluLWFjY2Vzcy1ncm91cHM8L2tleT4KCQk8YXJyYXk+CgkJCQk8c3RyaW5nPktUVDlHODU5TVIuKjwvc3RyaW5nPgoJCQkJPHN0cmluZz5jb20uYXBwbGUudG9rZW48L3N0cmluZz4KCQk8L2FycmF5PgoJCQkJCgkJCQk8a2V5PmdldC10YXNrLWFsbG93PC9rZXk+CgkJPGZhbHNlLz4KCQkJCQoJCQkJPGtleT5jb20uYXBwbGUuZGV2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" -} diff --git a/docs/Changelog.md b/docs/Changelog.md index 4d0fcf7..74a2b08 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -1,9 +1,8 @@ # Changelog -## X.X.X (TBA) +## 1.13.0 (TBA) - Added status to `UserOperation` and removed redundant `OperationHistoryEntry` [(#171)](https://github.com/wultra/mtoken-sdk-ios/pull/171) -- Added option for Firebase Cloud Messaging for Push Notifications and automatic APNS environment detection [(#174)](https://github.com/wultra/mtoken-sdk-ios/issues/174). ## 1.12.0 (October 2024) diff --git a/docs/Using-Push-Service.md b/docs/Using-Push-Service.md index 715c821..1ae7610 100644 --- a/docs/Using-Push-Service.md +++ b/docs/Using-Push-Service.md @@ -11,7 +11,7 @@ ## Introduction -Push Service is responsible for registering the device for the push notifications about the operations that are tied to the current PowerAuth activation. +Push Service is responsible for registering the device for the push notifications about the Operations that are tied to the current PowerAuth activation. Note: Before using Push Service, you need to have a `PowerAuthSDK` object available and initialized with a valid activation. Without a valid PowerAuth activation, the service will return an error @@ -48,51 +48,28 @@ All available methods of the `WMTPush` API are: - `pushNotificationsRegisteredOnServer` - If there was already made a successful request. - `acceptLanguage` - Language settings, that will be sent along with each request. -- `register(to: WMTPushPlatform, completionHandler: completion: @escaping (Result) -> Void)` - Registers push token on the backend. - - `to` - Platform and data needed (APNS or FCM + push token) +- `registerDeviceTokenForPushNotifications(token: Data, completionHandler: @escaping (_ success: Bool, _ error: WMTError?) -> Void)` - Registers push token on the backend. + - `token` - token data retrieved from APNS. - `completionHandler` - Called when the request finishes. Always called on the main thread. ## Registering to WMT Push Notifications -### Using APNS (Apple Push Notification Service) - -To register your app to push notifications regarding the operations, you can simply call the `register` method with `.apns` platform parameter: +To register your app to push notifications regarding the operations, you can simply call the `registerDeviceTokenForPushNotifications` method: ```swift // UIApplicationDelegate method func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { - pushService.register(to: .apns(token: deviceToken)) { result in - if case .failure(let error) = result { - // registration failed - } - } -} -``` - - -The above method will get called only if you registered the app to receive push notifications. For more information, visit the [official apple documentation](https://developer.apple.com/documentation/usernotifications/handling_notifications_and_notification-related_actions). - - -### Using FCM (Firebase Cloud Messaging) - -To register your app to push notifications regarding the operations, you can simply call the `register` method with `.fcm` platform parameter: - -```swift -func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { - guard let fcmToken else { - // token not received - return - } - pushService.register(to: .fcm(token: fcmToken)) { result in - if case .failure(let error) = result { - // registration failed + pushService.registerDeviceTokenForPushNotifications(token: deviceToken) { success, error in + guard success else { + // push registration failed + return } } } ``` -To properly configure FCM for iOS, follow [official google documentation](https://firebase.google.com/docs/cloud-messaging/ios/client). +The above method will get called only if you registered the app to receive push notifications. For more information, visit the [official documentation](https://developer.apple.com/documentation/usernotifications/handling_notifications_and_notification-related_actions). ## Receiving WMT Push Notifications diff --git a/scripts/test.sh b/scripts/test.sh index d2ada43..ba911fe 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -14,7 +14,6 @@ CL_LGN="" CL_PWD="" CL_AID="" ER_URL="" -PU_URL="" OP_URL="" IN_URL="" SDKCONFIG="" @@ -58,11 +57,6 @@ do shift shift ;; - -pu) - PU_URL="$2" - shift - shift - ;; -in) IN_URL="$2" shift @@ -95,7 +89,6 @@ echo """{ \"cloudApplicationId\" : \"${CL_AID}\", \"enrollmentServerUrl\" : \"${ER_URL}\", \"operationsServerUrl\" : \"${OP_URL}\", - \"pushServerUrl\" : \"${PU_URL}\", \"inboxServerUrl\" : \"${IN_URL}\", \"sdkConfig\" : \"${SDKCONFIG}\" }""" > "WultraMobileTokenSDKTests/Configs/config.json"